[PATCH 1/7] cipher: fix spurious AEAD byte-counter carry for 4 GiB adds
Jussi Kivilinna
jussi.kivilinna at iki.fi
Fri Jul 24 20:48:00 CEST 2026
* cipher/cipher-internal.h (cipher_bytecounter_add): New.
* cipher/cipher-gcm.c (gcm_bytecounter_add): Use shared helper.
* cipher/cipher-gcm-siv.c (gcm_siv_bytecounter_add): Likewise.
* cipher/cipher-poly1305.c (poly1305_bytecounter_add): Likewise.
* tests/Makefile.am (tests_bin): Add 't-cipher-internal'.
(t_cipher_internal_CPPFLAGS): New.
* tests/t-cipher-internal.c: New test.
--
Byte-counter helpers added high half of size_t addend, then detected
low-word carry by comparing updated low word against full 64-bit
addend. For single add of 4 GiB or more this comparison always
caused carry and incremented high word second time. Adding 4 GiB to
zero counter gave high word 2 instead of 1.
Detect carry against truncated low addend instead and use single shared
helper for all three modes. New test checks arithmetic across 32/64-bit
boundaries.
Reported-by: JEAN Jeremy <Jeremy.Jean at ssi.gouv.fr>
Signed-off-by: Jussi Kivilinna <jussi.kivilinna at iki.fi>
---
cipher/cipher-gcm-siv.c | 11 +--
cipher/cipher-gcm.c | 11 +--
cipher/cipher-internal.h | 24 ++++++
cipher/cipher-poly1305.c | 17 +----
tests/Makefile.am | 4 +-
tests/t-cipher-internal.c | 157 ++++++++++++++++++++++++++++++++++++++
6 files changed, 187 insertions(+), 37 deletions(-)
create mode 100644 tests/t-cipher-internal.c
diff --git a/cipher/cipher-gcm-siv.c b/cipher/cipher-gcm-siv.c
index b92ae45d..6e0ce347 100644
--- a/cipher/cipher-gcm-siv.c
+++ b/cipher/cipher-gcm-siv.c
@@ -50,16 +50,7 @@ mulx_ghash (byte *a)
static inline void
gcm_siv_bytecounter_add (u32 ctr[2], size_t add)
{
- if (sizeof(add) > sizeof(u32))
- {
- u32 high_add = ((add >> 31) >> 1) & 0xffffffff;
- ctr[1] += high_add;
- }
-
- ctr[0] += add;
- if (ctr[0] >= add)
- return;
- ++ctr[1];
+ cipher_bytecounter_add (ctr, add);
}
diff --git a/cipher/cipher-gcm.c b/cipher/cipher-gcm.c
index 1627cd1c..c04683f8 100644
--- a/cipher/cipher-gcm.c
+++ b/cipher/cipher-gcm.c
@@ -692,16 +692,7 @@ setupM (gcry_cipher_hd_t c)
static inline void
gcm_bytecounter_add (u32 ctr[2], size_t add)
{
- if (sizeof(add) > sizeof(u32))
- {
- u32 high_add = ((add >> 31) >> 1) & 0xffffffff;
- ctr[1] += high_add;
- }
-
- ctr[0] += add;
- if (ctr[0] >= add)
- return;
- ++ctr[1];
+ cipher_bytecounter_add (ctr, add);
}
diff --git a/cipher/cipher-internal.h b/cipher/cipher-internal.h
index eabb7ea9..8e6cbffc 100644
--- a/cipher/cipher-internal.h
+++ b/cipher/cipher-internal.h
@@ -808,6 +808,30 @@ static inline unsigned int _gcry_blocksize_shift(gcry_cipher_hd_t c)
}
+/* Add 32-bit or 64-bit size_t to 2x32-bit byte counter. */
+static inline int
+cipher_bytecounter_add (u32 ctr[2], size_t add)
+{
+ int overflow = 0;
+ u32 low_add = add;
+
+ if (sizeof(add) > sizeof(u32))
+ {
+ u32 high_add = ((add >> 31) >> 1) & 0xffffffff;
+ ctr[1] += high_add;
+ if (ctr[1] < high_add)
+ overflow = 1;
+ }
+
+ ctr[0] += low_add;
+ if (ctr[0] >= low_add)
+ return overflow;
+
+ ctr[1] += 1;
+ return (ctr[1] < 1) || overflow;
+}
+
+
/* Optimized function for adding value to cipher block. */
static inline void
cipher_block_add(void *_dstsrc, unsigned int add, size_t blocksize)
diff --git a/cipher/cipher-poly1305.c b/cipher/cipher-poly1305.c
index c76dd9a4..c7441971 100644
--- a/cipher/cipher-poly1305.c
+++ b/cipher/cipher-poly1305.c
@@ -33,22 +33,7 @@
static inline int
poly1305_bytecounter_add (u32 ctr[2], size_t add)
{
- int overflow = 0;
-
- if (sizeof(add) > sizeof(u32))
- {
- u32 high_add = ((add >> 31) >> 1) & 0xffffffff;
- ctr[1] += high_add;
- if (ctr[1] < high_add)
- overflow = 1;
- }
-
- ctr[0] += add;
- if (ctr[0] >= add)
- return overflow;
-
- ctr[1] += 1;
- return (ctr[1] < 1) || overflow;
+ return cipher_bytecounter_add (ctr, add);
}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 9392dd36..8fcde4fc 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -25,7 +25,8 @@ tests_bin = \
version t-secmem mpitests t-sexp t-convert \
t-mpi-bit t-mpi-point t-lock \
prime basic keygen pubkey hmac hashtest t-kdf keygrip \
- aeswrap random t-kem t-thread-local t-fips-service-ind
+ aeswrap random t-kem t-thread-local t-fips-service-ind \
+ t-cipher-internal
if USE_RSA
tests_bin += pkcs1v2 t-rsa-pss t-rsa-15 t-rsa-testparm
@@ -104,6 +105,7 @@ t_lock_CFLAGS = $(GPG_ERROR_MT_CFLAGS)
t_thread_local_LDADD = $(standard_ldadd) $(GPG_ERROR_MT_LIBS) @LDADD_FOR_TESTS_KLUDGE@
t_thread_local_CFLAGS = $(GPG_ERROR_MT_CFLAGS)
testdrv_LDADD = $(LDADD_FOR_TESTS_KLUDGE)
+t_cipher_internal_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/cipher
# Build a version of the test driver for the build platform.
testdrv-build: testdrv.c
diff --git a/tests/t-cipher-internal.c b/tests/t-cipher-internal.c
new file mode 100644
index 00000000..6a9cdc7d
--- /dev/null
+++ b/tests/t-cipher-internal.c
@@ -0,0 +1,157 @@
+/* t-cipher-internal.c - Regression tests for cipher-internal.h helpers
+ * Copyright (C) 2026 Jussi Kivilinna <jussi.kivilinna at iki.fi>
+ *
+ * This file is part of Libgcrypt.
+ *
+ * Libgcrypt is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Libgcrypt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../src/g10lib.h"
+#include "../cipher/cipher-internal.h"
+
+#define PGM "t-cipher-internal"
+
+static int verbose;
+static int debug;
+static int error_count;
+
+static void
+print_hilo (const char *label, u64 v)
+{
+ fprintf (stderr, "%s=%08lx%08lx", label,
+ (unsigned long)(u32)(v >> 32), (unsigned long)(u32)v);
+}
+
+static void
+check_bytecounter_add (u64 start, u64 add)
+{
+ u32 ctr[2];
+ u64 want = start + add;
+ int want_ovf = add > (~(u64)0 - start);
+ int got_ovf;
+
+ ctr[0] = (u32)start;
+ ctr[1] = (u32)(start >> 32);
+
+ got_ovf = cipher_bytecounter_add (ctr, (size_t)add);
+
+ if ((((u64)ctr[1] << 32) | ctr[0]) != want || !!got_ovf != !!want_ovf)
+ {
+ error_count++;
+ print_hilo ("FAIL: start", start);
+ print_hilo (" add", add);
+ print_hilo (" -> got", ((u64)ctr[1] << 32) | ctr[0]);
+ fprintf (stderr, " ovf=%d,", !!got_ovf);
+ print_hilo (" want", want);
+ fprintf (stderr, " ovf=%d\n", !!want_ovf);
+ }
+ else if (debug)
+ {
+ print_hilo ("ok: start", start);
+ print_hilo (" add", add);
+ print_hilo (" -> ", want);
+ fprintf (stderr, " ovf=%d\n", !!want_ovf);
+ }
+}
+
+static void
+test_bytecounter_add (void)
+{
+ static const u64 starts[] = {
+ U64_C (0x0000000000000000), U64_C (0x0000000000000001),
+ U64_C (0x0000000090000000), U64_C (0x00000000ffffffff),
+ U64_C (0x00000001ffffffff), U64_C (0xfffffffe00000000),
+ U64_C (0x123456789abcdef0)
+ };
+ static const u64 adds[] = {
+ U64_C (0), U64_C (1), U64_C (15), U64_C (16),
+ U64_C (0x000000007fffffff), U64_C (0x0000000080000000),
+ U64_C (0x00000000ffffffff), U64_C (0x0000000100000000),
+ U64_C (0x0000000100000010), U64_C (0x0000000180000000),
+ U64_C (0x0000001000000000), U64_C (0x000000fffffffff0),
+ U64_C (0xffffffffffffffff)
+ };
+ unsigned int s, a;
+
+ if (verbose)
+ fprintf (stderr, " checking cipher_bytecounter_add\n");
+
+ for (s = 0; s < DIM (starts); s++)
+ for (a = 0; a < DIM (adds); a++)
+ {
+ if (sizeof (size_t) < 8 && adds[a] > U64_C (0xffffffff))
+ continue;
+ check_bytecounter_add (starts[s], adds[a]);
+ }
+
+ if (sizeof (size_t) > 4)
+ {
+ u32 ctr[2] = { 0, 0 };
+ cipher_bytecounter_add (ctr, (size_t)U64_C (0x100000000));
+ if (ctr[0] != 0 || ctr[1] != 1)
+ {
+ error_count++;
+ fprintf (stderr, "FAIL: 4 GiB add gave %08lx:%08lx,"
+ " expected 00000001:00000000\n",
+ (unsigned long)ctr[1], (unsigned long)ctr[0]);
+ }
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ int last_argc = -1;
+
+ if (argc)
+ { argc--; argv++; }
+
+ while (argc && last_argc != argc)
+ {
+ last_argc = argc;
+ if (!strcmp (*argv, "--"))
+ {
+ argc--; argv++;
+ break;
+ }
+ else if (!strcmp (*argv, "--verbose"))
+ {
+ verbose++;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "--debug"))
+ {
+ verbose = debug = 1;
+ argc--; argv++;
+ }
+ }
+
+ if (verbose)
+ fprintf (stderr, "Starting cipher-internal checks.\n");
+
+ test_bytecounter_add ();
+
+ if (error_count)
+ fprintf (stderr, PGM ": %d test(s) failed\n", error_count);
+
+ return !!error_count;
+}
--
2.53.0
More information about the Gcrypt-devel
mailing list