[PATCH 1/7] cipher: fix spurious AEAD byte-counter carry for 4 GiB adds
Jussi Kivilinna <[email protected]> Fri, 24 Jul 2026 21:48:00 +0300
| Newsgroups | gmane.comp.encryption.gpg.libgcrypt.devel |
|---|---|
| Message-ID | <[email protected]> |
* 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 <[email protected]> Signed-off-by: Jussi Kivilinna <[email protected]> --- 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 <[email protected]> + * + * 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