From e05f16967735b1a7f184406269e1b7959949b17d Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Thu, 14 May 2026 15:02:28 +0100 Subject: [PATCH 01/28] Add test for chacha20 counter overflow bug Signed-off-by: Ben Taylor --- tests/suites/test_suite_chacha20.data | 3 ++ tests/suites/test_suite_chacha20.function | 41 +++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/tests/suites/test_suite_chacha20.data b/tests/suites/test_suite_chacha20.data index 86094604bf..d28d771264 100644 --- a/tests/suites/test_suite_chacha20.data +++ b/tests/suites/test_suite_chacha20.data @@ -22,5 +22,8 @@ chacha20_crypt:"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0 ChaCha20 RFC 7539 Test Vector #3 (Decrypt) chacha20_crypt:"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0":"000000000000000000000002":42:"62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553ebf39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f7704c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1":"2754776173206272696c6c69672c20616e642074686520736c6974687920746f7665730a446964206779726520616e642067696d626c6520696e2074686520776162653a0a416c6c206d696d737920776572652074686520626f726f676f7665732c0a416e6420746865206d6f6d65207261746873206f757467726162652e" +ChaCha20 Counter Overflow +chacha20_input_len_too_long: + ChaCha20 Selftest chacha20_self_test: diff --git a/tests/suites/test_suite_chacha20.function b/tests/suites/test_suite_chacha20.function index d6b67e12f2..0e8dd91324 100644 --- a/tests/suites/test_suite_chacha20.function +++ b/tests/suites/test_suite_chacha20.function @@ -67,6 +67,47 @@ void chacha20_crypt(data_t *key_str, } /* END_CASE */ +/* BEGIN_CASE */ +void chacha20_input_len_too_long(void) +{ + mbedtls_chacha20_context ctx; + unsigned char key[32] = { 0 }; + unsigned char nonce[12] = { 0 }; + unsigned char input[65] = { 0 }; + unsigned char output[65] = { 0 }; + size_t i; + + mbedtls_chacha20_init(&ctx); + + TEST_ASSERT(mbedtls_chacha20_setkey(&ctx, key) == 0); + TEST_ASSERT(mbedtls_chacha20_starts(&ctx, nonce, UINT32_MAX) == 0); + + /* + * An overlong call must fail before consuming buffered keystream bytes, + * writing partial output, or advancing the context. + */ + TEST_EQUAL(mbedtls_chacha20_update(&ctx, 1, input, output), 0); + memset(output, 0x2a, sizeof(output)); + TEST_EQUAL(mbedtls_chacha20_update(&ctx, 64, input, output), + MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); + for (i = 0; i < sizeof(output); i++) { + TEST_EQUAL(output[i], 0x2a); + } + + /* Consume the rest of the final counter block, then reject more input. */ + TEST_EQUAL(mbedtls_chacha20_update(&ctx, 63, input, output), 0); + TEST_EQUAL(mbedtls_chacha20_update(&ctx, 1, input, output), + MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); + + /* The one-shot API should report the same error. */ + TEST_EQUAL(mbedtls_chacha20_crypt(key, nonce, UINT32_MAX, + sizeof(input), input, output), + MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); + + mbedtls_chacha20_free(&ctx); +} +/* END_CASE */ + /* BEGIN_CASE depends_on:MBEDTLS_SELF_TEST */ void chacha20_self_test() { From 515f81c852d0b3aaa99d034a196e775721473db4 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Mon, 18 May 2026 10:08:48 +0100 Subject: [PATCH 02/28] Add ChangeLog Signed-off-by: Ben Taylor --- ChangeLog.d/chacha20-counter-overflow.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ChangeLog.d/chacha20-counter-overflow.txt diff --git a/ChangeLog.d/chacha20-counter-overflow.txt b/ChangeLog.d/chacha20-counter-overflow.txt new file mode 100644 index 0000000000..10ce71f081 --- /dev/null +++ b/ChangeLog.d/chacha20-counter-overflow.txt @@ -0,0 +1,4 @@ +Security + * Reject ChaCha20 operations that would make the 32-bit block counter wrap + around, which could otherwise reuse keystream and compromise + confidentiality. Fixes #1604. From 609b1c51b1a6fdb23210075607529a0505e0c4f9 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Thu, 14 May 2026 15:02:29 +0100 Subject: [PATCH 03/28] Add fix for chacha20 counter overflow bug Signed-off-by: Ben Taylor --- include/mbedtls/chacha20.h | 7 +++++++ library/chacha20.c | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/include/mbedtls/chacha20.h b/include/mbedtls/chacha20.h index 680fe36046..b6ca5605af 100644 --- a/include/mbedtls/chacha20.h +++ b/include/mbedtls/chacha20.h @@ -37,6 +37,7 @@ extern "C" { typedef struct mbedtls_chacha20_context { uint32_t MBEDTLS_PRIVATE(state)[16]; /*! The state (before round operations). */ + uint64_t MBEDTLS_PRIVATE(remaining_blocks); /*! Number of keystream blocks that can still be generated. */ uint8_t MBEDTLS_PRIVATE(keystream8)[64]; /*! Leftover keystream bytes. */ size_t MBEDTLS_PRIVATE(keystream_bytes_used); /*! Number of keystream bytes already used. */ } @@ -143,6 +144,9 @@ int mbedtls_chacha20_starts(mbedtls_chacha20_context *ctx, * This pointer can be \c NULL if `size == 0`. * * \return \c 0 on success. + * \return #MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA + * if processing \p size bytes would make the 32-bit block + * counter wrap. * \return A negative error code on failure. */ int mbedtls_chacha20_update(mbedtls_chacha20_context *ctx, @@ -176,6 +180,9 @@ int mbedtls_chacha20_update(mbedtls_chacha20_context *ctx, * This pointer can be \c NULL if `size == 0`. * * \return \c 0 on success. + * \return #MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA + * if processing \p size bytes would make the 32-bit block + * counter wrap. * \return A negative error code on failure. */ int mbedtls_chacha20_crypt(const unsigned char key[32], diff --git a/library/chacha20.c b/library/chacha20.c index acaae5b2e9..0c70cdb526 100644 --- a/library/chacha20.c +++ b/library/chacha20.c @@ -31,6 +31,8 @@ #define CHACHA20_BLOCK_SIZE_BYTES (4U * 16U) +#define CHACHA20_MAX_BLOCKS (UINT64_C(1) << 32) + /** * \brief ChaCha20 quarter round operation. * @@ -184,6 +186,7 @@ int mbedtls_chacha20_starts(mbedtls_chacha20_context *ctx, { /* Counter */ ctx->state[12] = counter; + ctx->remaining_blocks = CHACHA20_MAX_BLOCKS - counter; /* Nonce */ ctx->state[13] = MBEDTLS_GET_UINT32_LE(nonce, 0); @@ -198,12 +201,48 @@ int mbedtls_chacha20_starts(mbedtls_chacha20_context *ctx, return 0; } +static int chacha20_check_remaining_blocks(const mbedtls_chacha20_context *ctx, + size_t size) +{ + size_t available_keystream = 0; + uint64_t needed_blocks = 0; + + if (ctx->keystream_bytes_used < CHACHA20_BLOCK_SIZE_BYTES) { + available_keystream = + CHACHA20_BLOCK_SIZE_BYTES - ctx->keystream_bytes_used; + + if (size <= available_keystream) { + return 0; + } + + size -= available_keystream; + } + + needed_blocks = (uint64_t) (size / CHACHA20_BLOCK_SIZE_BYTES); + + if (size % CHACHA20_BLOCK_SIZE_BYTES != 0U) { + needed_blocks++; + } + + if (needed_blocks > ctx->remaining_blocks) { + return MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA; + } + + return 0; +} + int mbedtls_chacha20_update(mbedtls_chacha20_context *ctx, size_t size, const unsigned char *input, unsigned char *output) { size_t offset = 0U; + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + + ret = chacha20_check_remaining_blocks(ctx, size); + if (ret != 0) { + return ret; + } /* Use leftover keystream bytes, if available */ while (size > 0U && ctx->keystream_bytes_used < CHACHA20_BLOCK_SIZE_BYTES) { @@ -219,6 +258,7 @@ int mbedtls_chacha20_update(mbedtls_chacha20_context *ctx, while (size >= CHACHA20_BLOCK_SIZE_BYTES) { /* Generate new keystream block and increment counter */ chacha20_block(ctx->state, ctx->keystream8); + ctx->remaining_blocks--; ctx->state[CHACHA20_CTR_INDEX]++; mbedtls_xor(output + offset, input + offset, ctx->keystream8, 64U); @@ -231,6 +271,7 @@ int mbedtls_chacha20_update(mbedtls_chacha20_context *ctx, if (size > 0U) { /* Generate new keystream block and increment counter */ chacha20_block(ctx->state, ctx->keystream8); + ctx->remaining_blocks--; ctx->state[CHACHA20_CTR_INDEX]++; mbedtls_xor(output + offset, input + offset, ctx->keystream8, size); From 56b050d7386440555bf2077ea0f0334b0bae1ee9 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Fri, 22 May 2026 08:38:38 +0100 Subject: [PATCH 04/28] Add use of keystream_bytes_used as the exhausted marker Signed-off-by: Ben Taylor --- include/mbedtls/chacha20.h | 1 - library/chacha20.c | 36 +++++++++++++++++------ tests/suites/test_suite_chacha20.function | 4 ++- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/include/mbedtls/chacha20.h b/include/mbedtls/chacha20.h index b6ca5605af..172deb6967 100644 --- a/include/mbedtls/chacha20.h +++ b/include/mbedtls/chacha20.h @@ -37,7 +37,6 @@ extern "C" { typedef struct mbedtls_chacha20_context { uint32_t MBEDTLS_PRIVATE(state)[16]; /*! The state (before round operations). */ - uint64_t MBEDTLS_PRIVATE(remaining_blocks); /*! Number of keystream blocks that can still be generated. */ uint8_t MBEDTLS_PRIVATE(keystream8)[64]; /*! Leftover keystream bytes. */ size_t MBEDTLS_PRIVATE(keystream_bytes_used); /*! Number of keystream bytes already used. */ } diff --git a/library/chacha20.c b/library/chacha20.c index 0c70cdb526..45bef2c090 100644 --- a/library/chacha20.c +++ b/library/chacha20.c @@ -31,7 +31,9 @@ #define CHACHA20_BLOCK_SIZE_BYTES (4U * 16U) -#define CHACHA20_MAX_BLOCKS (UINT64_C(1) << 32) +#define CHACHA20_MAX_BLOCKS 0xFFFFFFFF + +#define CHACHA20_COUNTER_EXHAUSTED (CHACHA20_BLOCK_SIZE_BYTES + 1U) /** * \brief ChaCha20 quarter round operation. @@ -185,8 +187,7 @@ int mbedtls_chacha20_starts(mbedtls_chacha20_context *ctx, uint32_t counter) { /* Counter */ - ctx->state[12] = counter; - ctx->remaining_blocks = CHACHA20_MAX_BLOCKS - counter; + ctx->state[CHACHA20_CTR_INDEX] = counter; /* Nonce */ ctx->state[13] = MBEDTLS_GET_UINT32_LE(nonce, 0); @@ -201,12 +202,16 @@ int mbedtls_chacha20_starts(mbedtls_chacha20_context *ctx, return 0; } -static int chacha20_check_remaining_blocks(const mbedtls_chacha20_context *ctx, - size_t size) +static int chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, + size_t size) { size_t available_keystream = 0; uint64_t needed_blocks = 0; + if (ctx->keystream_bytes_used == CHACHA20_COUNTER_EXHAUSTED) { + return size == 0U ? 0 : MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA; + } + if (ctx->keystream_bytes_used < CHACHA20_BLOCK_SIZE_BYTES) { available_keystream = CHACHA20_BLOCK_SIZE_BYTES - ctx->keystream_bytes_used; @@ -215,6 +220,10 @@ static int chacha20_check_remaining_blocks(const mbedtls_chacha20_context *ctx, return 0; } + if (ctx->state[CHACHA20_CTR_INDEX] == 0U) { + return MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA; + } + size -= available_keystream; } @@ -224,7 +233,9 @@ static int chacha20_check_remaining_blocks(const mbedtls_chacha20_context *ctx, needed_blocks++; } - if (needed_blocks > ctx->remaining_blocks) { + if (needed_blocks != 0U && + needed_blocks - 1U > + (uint64_t) CHACHA20_MAX_BLOCKS - ctx->state[CHACHA20_CTR_INDEX]) { return MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA; } @@ -239,7 +250,7 @@ int mbedtls_chacha20_update(mbedtls_chacha20_context *ctx, size_t offset = 0U; int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; - ret = chacha20_check_remaining_blocks(ctx, size); + ret = chacha20_check_counter_wrap(ctx, size); if (ret != 0) { return ret; } @@ -252,26 +263,33 @@ int mbedtls_chacha20_update(mbedtls_chacha20_context *ctx, ctx->keystream_bytes_used++; offset++; size--; + + if (ctx->keystream_bytes_used == CHACHA20_BLOCK_SIZE_BYTES && + ctx->state[CHACHA20_CTR_INDEX] == 0U) { + ctx->keystream_bytes_used = CHACHA20_COUNTER_EXHAUSTED; + } } /* Process full blocks */ while (size >= CHACHA20_BLOCK_SIZE_BYTES) { /* Generate new keystream block and increment counter */ chacha20_block(ctx->state, ctx->keystream8); - ctx->remaining_blocks--; ctx->state[CHACHA20_CTR_INDEX]++; mbedtls_xor(output + offset, input + offset, ctx->keystream8, 64U); offset += CHACHA20_BLOCK_SIZE_BYTES; size -= CHACHA20_BLOCK_SIZE_BYTES; + + if (ctx->state[CHACHA20_CTR_INDEX] == 0U) { + ctx->keystream_bytes_used = CHACHA20_COUNTER_EXHAUSTED; + } } /* Last (partial) block */ if (size > 0U) { /* Generate new keystream block and increment counter */ chacha20_block(ctx->state, ctx->keystream8); - ctx->remaining_blocks--; ctx->state[CHACHA20_CTR_INDEX]++; mbedtls_xor(output + offset, input + offset, ctx->keystream8, size); diff --git a/tests/suites/test_suite_chacha20.function b/tests/suites/test_suite_chacha20.function index 0e8dd91324..2163fd032d 100644 --- a/tests/suites/test_suite_chacha20.function +++ b/tests/suites/test_suite_chacha20.function @@ -99,7 +99,9 @@ void chacha20_input_len_too_long(void) TEST_EQUAL(mbedtls_chacha20_update(&ctx, 1, input, output), MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); - /* The one-shot API should report the same error. */ + /* The final counter block is valid, but generating another block is not. */ + TEST_EQUAL(mbedtls_chacha20_crypt(key, nonce, UINT32_MAX, + 64U, input, output), 0); TEST_EQUAL(mbedtls_chacha20_crypt(key, nonce, UINT32_MAX, sizeof(input), input, output), MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); From 0e9be79ea38cdfd608ef13171962b79d35121a51 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Wed, 27 May 2026 08:22:21 +0100 Subject: [PATCH 05/28] Add simplification of if statement in chacha20 Signed-off-by: Ben Taylor --- library/chacha20.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/library/chacha20.c b/library/chacha20.c index 45bef2c090..bef493a6dd 100644 --- a/library/chacha20.c +++ b/library/chacha20.c @@ -233,9 +233,7 @@ static int chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, needed_blocks++; } - if (needed_blocks != 0U && - needed_blocks - 1U > - (uint64_t) CHACHA20_MAX_BLOCKS - ctx->state[CHACHA20_CTR_INDEX]) { + if (needed_blocks > (uint64_t) CHACHA20_MAX_BLOCKS + 1 - ctx->state[CHACHA20_CTR_INDEX]) { return MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA; } From 130b914ff2e5babf37b9fa5c3cacf0ecc6d26a76 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Wed, 27 May 2026 09:02:12 +0100 Subject: [PATCH 06/28] remove duplicate chacha20_check_counter_wrap functionality in chacha20 and chacha20_neon Signed-off-by: Ben Taylor --- library/chacha20.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/chacha20.c b/library/chacha20.c index bef493a6dd..205befb34b 100644 --- a/library/chacha20.c +++ b/library/chacha20.c @@ -233,7 +233,8 @@ static int chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, needed_blocks++; } - if (needed_blocks > (uint64_t) CHACHA20_MAX_BLOCKS + 1 - ctx->state[CHACHA20_CTR_INDEX]) { + if (needed_blocks > + (uint64_t) CHACHA20_MAX_BLOCKS + 1U - ctx->state[CHACHA20_CTR_INDEX]) { return MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA; } From 3a358b06774803ec1d7b5370cfa5bc3368944206 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Wed, 27 May 2026 15:06:16 +0100 Subject: [PATCH 07/28] Remove unused cast Signed-off-by: Ben Taylor --- library/chacha20.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/chacha20.c b/library/chacha20.c index 205befb34b..484bf7cfdf 100644 --- a/library/chacha20.c +++ b/library/chacha20.c @@ -227,7 +227,7 @@ static int chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, size -= available_keystream; } - needed_blocks = (uint64_t) (size / CHACHA20_BLOCK_SIZE_BYTES); + needed_blocks = (size / CHACHA20_BLOCK_SIZE_BYTES); if (size % CHACHA20_BLOCK_SIZE_BYTES != 0U) { needed_blocks++; From 7bf0215f8d2a5b4ca5b56c4692ef09176a342e91 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Thu, 28 May 2026 08:53:13 +0100 Subject: [PATCH 08/28] Add guard to prevent wraparound if SIZE_MAX > UINT64_MAX in chacha20 Signed-off-by: Ben Taylor --- library/chacha20.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/chacha20.c b/library/chacha20.c index 484bf7cfdf..84289163fc 100644 --- a/library/chacha20.c +++ b/library/chacha20.c @@ -212,6 +212,12 @@ static int chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, return size == 0U ? 0 : MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA; } +#if SIZE_MAX >= UINT64_MAX + if (size > UINT64_MAX / 2) { + return MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA; + } +#endif + if (ctx->keystream_bytes_used < CHACHA20_BLOCK_SIZE_BYTES) { available_keystream = CHACHA20_BLOCK_SIZE_BYTES - ctx->keystream_bytes_used; From dcf3b117490bc1e275bee1ea5dc4bf0c4ff61082 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Fri, 29 May 2026 08:49:08 +0100 Subject: [PATCH 09/28] Tidy ChaCha20 counter limit handling Signed-off-by: Ben Taylor --- include/mbedtls/chacha20.h | 3 ++- library/chacha20.c | 2 +- tests/suites/test_suite_chacha20.function | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/mbedtls/chacha20.h b/include/mbedtls/chacha20.h index 172deb6967..c2c1bff86b 100644 --- a/include/mbedtls/chacha20.h +++ b/include/mbedtls/chacha20.h @@ -38,7 +38,8 @@ extern "C" { typedef struct mbedtls_chacha20_context { uint32_t MBEDTLS_PRIVATE(state)[16]; /*! The state (before round operations). */ uint8_t MBEDTLS_PRIVATE(keystream8)[64]; /*! Leftover keystream bytes. */ - size_t MBEDTLS_PRIVATE(keystream_bytes_used); /*! Number of keystream bytes already used. */ + size_t MBEDTLS_PRIVATE(keystream_bytes_used); /*! Number of keystream bytes already used, + * or an internal sentinel value. */ } mbedtls_chacha20_context; diff --git a/library/chacha20.c b/library/chacha20.c index 84289163fc..0f97116f0c 100644 --- a/library/chacha20.c +++ b/library/chacha20.c @@ -31,7 +31,7 @@ #define CHACHA20_BLOCK_SIZE_BYTES (4U * 16U) -#define CHACHA20_MAX_BLOCKS 0xFFFFFFFF +#define CHACHA20_MAX_BLOCKS UINT32_MAX #define CHACHA20_COUNTER_EXHAUSTED (CHACHA20_BLOCK_SIZE_BYTES + 1U) diff --git a/tests/suites/test_suite_chacha20.function b/tests/suites/test_suite_chacha20.function index 2163fd032d..d8cc8a0eb3 100644 --- a/tests/suites/test_suite_chacha20.function +++ b/tests/suites/test_suite_chacha20.function @@ -96,6 +96,7 @@ void chacha20_input_len_too_long(void) /* Consume the rest of the final counter block, then reject more input. */ TEST_EQUAL(mbedtls_chacha20_update(&ctx, 63, input, output), 0); + TEST_EQUAL(mbedtls_chacha20_update(&ctx, 0, NULL, NULL), 0); TEST_EQUAL(mbedtls_chacha20_update(&ctx, 1, input, output), MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); From 3df9fac12962707c27bd11ea51bbe6d8783bba08 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Fri, 29 May 2026 08:49:26 +0100 Subject: [PATCH 10/28] Reject overlong ChaChaPoly updates Signed-off-by: Ben Taylor --- library/chachapoly.c | 5 ++++ tests/suites/test_suite_chachapoly.data | 3 +++ tests/suites/test_suite_chachapoly.function | 29 +++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/library/chachapoly.c b/library/chachapoly.c index a1314eab6d..a6fbebe10e 100644 --- a/library/chachapoly.c +++ b/library/chachapoly.c @@ -165,6 +165,11 @@ int mbedtls_chachapoly_update(mbedtls_chachapoly_context *ctx, return MBEDTLS_ERR_CHACHAPOLY_BAD_STATE; } + ret = mbedtls_chacha20_check_counter_wrap(&ctx->chacha20_ctx, len); + if (ret != 0) { + return ret; + } + if (ctx->state == CHACHAPOLY_STATE_AAD) { ctx->state = CHACHAPOLY_STATE_CIPHERTEXT; diff --git a/tests/suites/test_suite_chachapoly.data b/tests/suites/test_suite_chachapoly.data index 02c7bf3d21..1c4c670953 100644 --- a/tests/suites/test_suite_chachapoly.data +++ b/tests/suites/test_suite_chachapoly.data @@ -19,6 +19,9 @@ mbedtls_chachapoly_dec:"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc ChaCha20-Poly1305 State Flow chachapoly_state: +ChaCha20-Poly1305 overlong update preserves state +chachapoly_update_too_long_preserves_state: + ChaCha20-Poly1305 Selftest depends_on:MBEDTLS_SELF_TEST chachapoly_selftest: diff --git a/tests/suites/test_suite_chachapoly.function b/tests/suites/test_suite_chachapoly.function index 4f1a34e047..594eb09101 100644 --- a/tests/suites/test_suite_chachapoly.function +++ b/tests/suites/test_suite_chachapoly.function @@ -76,6 +76,35 @@ exit: } /* END_CASE */ +/* BEGIN_CASE */ +void chachapoly_update_too_long_preserves_state() +{ + unsigned char key[32] = { 0 }; + unsigned char nonce[12] = { 0 }; + unsigned char input[65] = { 0 }; + unsigned char output[65] = { 0 }; + unsigned char aad[1] = { 0 }; + mbedtls_chachapoly_context ctx; + + mbedtls_chachapoly_init(&ctx); + + TEST_EQUAL(mbedtls_chachapoly_setkey(&ctx, key), 0); + TEST_EQUAL(mbedtls_chachapoly_starts(&ctx, nonce, + MBEDTLS_CHACHAPOLY_ENCRYPT), 0); + + /* Force the next update to be too long without processing a huge buffer. */ + ctx.chacha20_ctx.state[12] = UINT32_MAX; + TEST_EQUAL(mbedtls_chachapoly_update(&ctx, sizeof(input), input, output), + MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); + TEST_EQUAL(ctx.ciphertext_len, 0); + + TEST_EQUAL(mbedtls_chachapoly_update_aad(&ctx, aad, sizeof(aad)), 0); + +exit: + mbedtls_chachapoly_free(&ctx); +} +/* END_CASE */ + /* BEGIN_CASE */ void chachapoly_state() { From 96cb584a5e108f091ca50de24f7ee51c70dd01bf Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Fri, 29 May 2026 08:49:51 +0100 Subject: [PATCH 11/28] Reject overlong PSA ChaChaPoly lengths Signed-off-by: Ben Taylor --- library/psa_crypto.c | 8 +++++++- tests/suites/test_suite_psa_crypto.function | 6 ++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/library/psa_crypto.c b/library/psa_crypto.c index 63b16f9284..723b561e2c 100644 --- a/library/psa_crypto.c +++ b/library/psa_crypto.c @@ -5357,7 +5357,13 @@ psa_status_t psa_aead_set_lengths(psa_aead_operation_t *operation, #endif /* PSA_WANT_ALG_CCM */ #if defined(PSA_WANT_ALG_CHACHA20_POLY1305) case PSA_ALG_CHACHA20_POLY1305: - /* No length restrictions for ChaChaPoly. */ +#if SIZE_MAX > UINT32_MAX + if (plaintext_length > (size_t) UINT32_MAX * + 64U) { + status = PSA_ERROR_INVALID_ARGUMENT; + goto exit; + } +#endif break; #endif /* PSA_WANT_ALG_CHACHA20_POLY1305 */ default: diff --git a/tests/suites/test_suite_psa_crypto.function b/tests/suites/test_suite_psa_crypto.function index fb278ddbda..e408bf05fb 100644 --- a/tests/suites/test_suite_psa_crypto.function +++ b/tests/suites/test_suite_psa_crypto.function @@ -6367,7 +6367,8 @@ void aead_multipart_state_test(int key_type_arg, data_t *key_data, #if SIZE_MAX > UINT32_MAX PSA_ASSERT(psa_aead_encrypt_setup(&operation, key, alg)); - if (operation.alg == PSA_ALG_GCM) { + if (operation.alg == PSA_ALG_GCM || + operation.alg == PSA_ALG_CHACHA20_POLY1305) { TEST_EQUAL(psa_aead_set_lengths(&operation, additional_data->len, SIZE_MAX), PSA_ERROR_INVALID_ARGUMENT); @@ -6388,7 +6389,8 @@ void aead_multipart_state_test(int key_type_arg, data_t *key_data, PSA_ASSERT(psa_aead_set_nonce(&operation, nonce->x, nonce->len)); - if (operation.alg == PSA_ALG_GCM) { + if (operation.alg == PSA_ALG_GCM || + operation.alg == PSA_ALG_CHACHA20_POLY1305) { TEST_EQUAL(psa_aead_set_lengths(&operation, additional_data->len, SIZE_MAX), PSA_ERROR_INVALID_ARGUMENT); From ea9848e14afac46c9ac0343b7cb1c1d97fc268cc Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Fri, 29 May 2026 13:09:39 +0100 Subject: [PATCH 12/28] Adapt ChaCha20 counter helper for 3.6 Signed-off-by: Ben Taylor --- library/chacha20.c | 6 +++--- library/chachapoly.c | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/library/chacha20.c b/library/chacha20.c index 0f97116f0c..b67e7ef40e 100644 --- a/library/chacha20.c +++ b/library/chacha20.c @@ -202,8 +202,8 @@ int mbedtls_chacha20_starts(mbedtls_chacha20_context *ctx, return 0; } -static int chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, - size_t size) +int mbedtls_chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, + size_t size) { size_t available_keystream = 0; uint64_t needed_blocks = 0; @@ -255,7 +255,7 @@ int mbedtls_chacha20_update(mbedtls_chacha20_context *ctx, size_t offset = 0U; int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; - ret = chacha20_check_counter_wrap(ctx, size); + ret = mbedtls_chacha20_check_counter_wrap(ctx, size); if (ret != 0) { return ret; } diff --git a/library/chachapoly.c b/library/chachapoly.c index a6fbebe10e..3580a36367 100644 --- a/library/chachapoly.c +++ b/library/chachapoly.c @@ -21,6 +21,9 @@ #if !defined(MBEDTLS_CHACHAPOLY_ALT) +int mbedtls_chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, + size_t size); + #define CHACHAPOLY_STATE_INIT (0) #define CHACHAPOLY_STATE_AAD (1) #define CHACHAPOLY_STATE_CIPHERTEXT (2) /* Encrypting or decrypting */ From b555410883ea0b5c714a6acf2b411b9e74bc7d59 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Mon, 1 Jun 2026 14:43:50 +0100 Subject: [PATCH 13/28] Add mbedtls_chacha20_check_counter_wrap to header Signed-off-by: Ben Taylor --- library/chacha20.c | 2 ++ library/chachapoly.c | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/library/chacha20.c b/library/chacha20.c index b67e7ef40e..53bf4900b6 100644 --- a/library/chacha20.c +++ b/library/chacha20.c @@ -20,6 +20,8 @@ #include #include +#include "chacha20_internal.h" + #include "mbedtls/platform.h" #if !defined(MBEDTLS_CHACHA20_ALT) diff --git a/library/chachapoly.c b/library/chachapoly.c index 3580a36367..5cda8d6b7e 100644 --- a/library/chachapoly.c +++ b/library/chachapoly.c @@ -17,13 +17,12 @@ #include +#include "chacha20_internal.h" + #include "mbedtls/platform.h" #if !defined(MBEDTLS_CHACHAPOLY_ALT) -int mbedtls_chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, - size_t size); - #define CHACHAPOLY_STATE_INIT (0) #define CHACHAPOLY_STATE_AAD (1) #define CHACHAPOLY_STATE_CIPHERTEXT (2) /* Encrypting or decrypting */ From 85f3f6f894487441444b90a62bebd85f316b0f2d Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Tue, 2 Jun 2026 08:19:02 +0100 Subject: [PATCH 14/28] Add missing header Signed-off-by: Ben Taylor --- library/chacha20_internal.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 library/chacha20_internal.h diff --git a/library/chacha20_internal.h b/library/chacha20_internal.h new file mode 100644 index 0000000000..7b2a062a99 --- /dev/null +++ b/library/chacha20_internal.h @@ -0,0 +1,21 @@ +/** + * \file chacha20_internal.h + * + * \brief Internal declarations for ChaCha20. + */ +/* + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + +#ifndef MBEDTLS_CHACHA20_INTERNAL_H +#define MBEDTLS_CHACHA20_INTERNAL_H + +#include "common.h" + +#include "mbedtls/chacha20.h" + +int mbedtls_chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, + size_t size); + +#endif /* MBEDTLS_CHACHA20_INTERNAL_H */ From 2ddd6a2f2522270333f79d5cf92e302115c5cd13 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Tue, 2 Jun 2026 13:50:32 +0100 Subject: [PATCH 15/28] Improve comments and guards Signed-off-by: Ben Taylor --- include/mbedtls/chachapoly.h | 9 +++++++++ include/psa/crypto.h | 2 ++ library/chacha20_internal.h | 2 ++ library/chachapoly.c | 2 ++ 4 files changed, 15 insertions(+) diff --git a/include/mbedtls/chachapoly.h b/include/mbedtls/chachapoly.h index 3dc21e380b..7faf7cd415 100644 --- a/include/mbedtls/chachapoly.h +++ b/include/mbedtls/chachapoly.h @@ -227,6 +227,9 @@ int mbedtls_chachapoly_update_aad(mbedtls_chachapoly_context *ctx, * \return #MBEDTLS_ERR_CHACHAPOLY_BAD_STATE * if the operation has not been started or has been * finished. + * \return #MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA + * if processing \p len bytes would make the 32-bit block + * counter wrap. * \return Another negative error code on other kinds of failure. */ int mbedtls_chachapoly_update(mbedtls_chachapoly_context *ctx, @@ -280,6 +283,9 @@ int mbedtls_chachapoly_finish(mbedtls_chachapoly_context *ctx, * is written. This must not be \c NULL. * * \return \c 0 on success. + * \return #MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA + * if processing \p length bytes would make the 32-bit block + * counter wrap. * \return A negative error code on failure. */ int mbedtls_chachapoly_encrypt_and_tag(mbedtls_chachapoly_context *ctx, @@ -314,6 +320,9 @@ int mbedtls_chachapoly_encrypt_and_tag(mbedtls_chachapoly_context *ctx, * \return \c 0 on success. * \return #MBEDTLS_ERR_CHACHAPOLY_AUTH_FAILED * if the data was not authentic. + * \return #MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA + * if processing \p length bytes would make the 32-bit block + * counter wrap. * \return Another negative error code on other kinds of failure. */ int mbedtls_chachapoly_auth_decrypt(mbedtls_chachapoly_context *ctx, diff --git a/include/psa/crypto.h b/include/psa/crypto.h index 2fe9f35ec3..34a622a17a 100644 --- a/include/psa/crypto.h +++ b/include/psa/crypto.h @@ -2470,6 +2470,8 @@ psa_status_t psa_aead_set_nonce(psa_aead_operation_t *operation, * psa_aead_set_nonce() or psa_aead_generate_nonce(). * * - For #PSA_ALG_CCM, calling this function is required. + * - For #PSA_ALG_CHACHA20_POLY1305, the plaintext length must not + * exceed `UINT32_MAX * 64` bytes. * - For the other AEAD algorithms defined in this specification, calling * this function is not required. * - For vendor-defined algorithm, refer to the vendor documentation. diff --git a/library/chacha20_internal.h b/library/chacha20_internal.h index 7b2a062a99..487d536007 100644 --- a/library/chacha20_internal.h +++ b/library/chacha20_internal.h @@ -15,7 +15,9 @@ #include "mbedtls/chacha20.h" +#if !defined(MBEDTLS_CHACHA20_ALT) int mbedtls_chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, size_t size); +#endif /* !MBEDTLS_CHACHA20_ALT */ #endif /* MBEDTLS_CHACHA20_INTERNAL_H */ diff --git a/library/chachapoly.c b/library/chachapoly.c index 5cda8d6b7e..c43b930c0f 100644 --- a/library/chachapoly.c +++ b/library/chachapoly.c @@ -167,10 +167,12 @@ int mbedtls_chachapoly_update(mbedtls_chachapoly_context *ctx, return MBEDTLS_ERR_CHACHAPOLY_BAD_STATE; } +#if !defined(MBEDTLS_CHACHA20_ALT) ret = mbedtls_chacha20_check_counter_wrap(&ctx->chacha20_ctx, len); if (ret != 0) { return ret; } +#endif /* !MBEDTLS_CHACHA20_ALT */ if (ctx->state == CHACHAPOLY_STATE_AAD) { ctx->state = CHACHAPOLY_STATE_CIPHERTEXT; From a6023aef67932515375234f40cbee85b08e6fc48 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Wed, 3 Jun 2026 11:10:36 +0100 Subject: [PATCH 16/28] update code docs Signed-off-by: Ben Taylor --- include/psa/crypto.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/include/psa/crypto.h b/include/psa/crypto.h index 34a622a17a..4ccbf5474c 100644 --- a/include/psa/crypto.h +++ b/include/psa/crypto.h @@ -2073,6 +2073,8 @@ psa_status_t psa_cipher_abort(psa_cipher_operation_t *operation); * \param[in] plaintext Data that will be authenticated and * encrypted. * \param plaintext_length Size of \p plaintext in bytes. + * For #PSA_ALG_CHACHA20_POLY1305, this must + * not exceed `UINT32_MAX * 64` bytes. * \param[out] ciphertext Output buffer for the authenticated and * encrypted data. The additional data is not * part of this output. For algorithms where the @@ -2099,7 +2101,8 @@ psa_status_t psa_cipher_abort(psa_cipher_operation_t *operation); * \retval #PSA_ERROR_INVALID_HANDLE \emptydescription * \retval #PSA_ERROR_NOT_PERMITTED \emptydescription * \retval #PSA_ERROR_INVALID_ARGUMENT - * \p key is not compatible with \p alg. + * \p key is not compatible with \p alg, or \p plaintext_length + * is not acceptable for \p alg. * \retval #PSA_ERROR_NOT_SUPPORTED * \p alg is not supported or is not an AEAD algorithm. * \retval #PSA_ERROR_INSUFFICIENT_MEMORY \emptydescription @@ -2150,6 +2153,10 @@ psa_status_t psa_aead_encrypt(mbedtls_svc_key_id_t key, * must contain the encrypted data followed * by the authentication tag. * \param ciphertext_length Size of \p ciphertext in bytes. + * For #PSA_ALG_CHACHA20_POLY1305, the + * encrypted data length, excluding the + * authentication tag, must not exceed + * `UINT32_MAX * 64` bytes. * \param[out] plaintext Output buffer for the decrypted data. * \param plaintext_size Size of the \p plaintext buffer in bytes. * This must be appropriate for the selected @@ -2172,7 +2179,8 @@ psa_status_t psa_aead_encrypt(mbedtls_svc_key_id_t key, * The ciphertext is not authentic. * \retval #PSA_ERROR_NOT_PERMITTED \emptydescription * \retval #PSA_ERROR_INVALID_ARGUMENT - * \p key is not compatible with \p alg. + * \p key is not compatible with \p alg, or the encrypted data length + * is not acceptable for \p alg. * \retval #PSA_ERROR_NOT_SUPPORTED * \p alg is not supported or is not an AEAD algorithm. * \retval #PSA_ERROR_INSUFFICIENT_MEMORY \emptydescription @@ -2620,7 +2628,8 @@ psa_status_t psa_aead_update_ad(psa_aead_operation_t *operation, * less than the additional data length that was previously * specified with psa_aead_set_lengths(), or * the total input length overflows the plaintext length that - * was previously specified with psa_aead_set_lengths(). + * was previously specified with psa_aead_set_lengths(), or + * the total message length is not acceptable for the algorithm. * \retval #PSA_ERROR_INSUFFICIENT_MEMORY \emptydescription * \retval #PSA_ERROR_COMMUNICATION_FAILURE \emptydescription * \retval #PSA_ERROR_HARDWARE_FAILURE \emptydescription From 8b7fca9ede7bdf1672ec06793a56f07fb4375b9f Mon Sep 17 00:00:00 2001 From: Ben Taylor <32939606+bjwtaylor@users.noreply.github.com> Date: Wed, 3 Jun 2026 10:55:23 +0100 Subject: [PATCH 17/28] Add improvement to chacha20 tests Co-authored-by: minosgalanakis <30719586+minosgalanakis@users.noreply.github.com> Signed-off-by: Ben Taylor <32939606+bjwtaylor@users.noreply.github.com> (cherry picked from commit 9d6a54795c96f7f0d7dfc230071dc16c5f47f94a) --- tests/suites/test_suite_chacha20.function | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/suites/test_suite_chacha20.function b/tests/suites/test_suite_chacha20.function index d8cc8a0eb3..2b8e086d1b 100644 --- a/tests/suites/test_suite_chacha20.function +++ b/tests/suites/test_suite_chacha20.function @@ -97,7 +97,9 @@ void chacha20_input_len_too_long(void) /* Consume the rest of the final counter block, then reject more input. */ TEST_EQUAL(mbedtls_chacha20_update(&ctx, 63, input, output), 0); TEST_EQUAL(mbedtls_chacha20_update(&ctx, 0, NULL, NULL), 0); - TEST_EQUAL(mbedtls_chacha20_update(&ctx, 1, input, output), +/*Test that zero length update does not break an exhausted context */ +TEST_EQUAL(mbedtls_chacha20_update(&ctx, 0, NULL, NULL), 0); +TEST_EQUAL(mbedtls_chacha20_update(&ctx, 1, input, output), MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); /* The final counter block is valid, but generating another block is not. */ From 08accf7b92c62feb5ebe2581da2965b0c7f60512 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Wed, 3 Jun 2026 13:36:14 +0100 Subject: [PATCH 18/28] Fix code style Signed-off-by: Ben Taylor (cherry picked from commit ef055ea1bbfd17de9eeae11c5f90101361fb02df) --- tests/suites/test_suite_chacha20.function | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/suites/test_suite_chacha20.function b/tests/suites/test_suite_chacha20.function index 2b8e086d1b..1f034a2120 100644 --- a/tests/suites/test_suite_chacha20.function +++ b/tests/suites/test_suite_chacha20.function @@ -98,8 +98,8 @@ void chacha20_input_len_too_long(void) TEST_EQUAL(mbedtls_chacha20_update(&ctx, 63, input, output), 0); TEST_EQUAL(mbedtls_chacha20_update(&ctx, 0, NULL, NULL), 0); /*Test that zero length update does not break an exhausted context */ -TEST_EQUAL(mbedtls_chacha20_update(&ctx, 0, NULL, NULL), 0); -TEST_EQUAL(mbedtls_chacha20_update(&ctx, 1, input, output), + TEST_EQUAL(mbedtls_chacha20_update(&ctx, 0, NULL, NULL), 0); + TEST_EQUAL(mbedtls_chacha20_update(&ctx, 1, input, output), MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); /* The final counter block is valid, but generating another block is not. */ From 98b1bf9f2eaea2889a9ac0ac1e21b3f3dfc689c8 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Wed, 3 Jun 2026 14:14:31 +0100 Subject: [PATCH 19/28] fix asserts in old chachapoly_update_too_long_preserves_state function Signed-off-by: Ben Taylor (cherry picked from commit 57ac277339e9e9684caf1e8a3bad20c57678ea83) --- tests/suites/test_suite_chachapoly.data | 4 ++-- tests/suites/test_suite_chachapoly.function | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/suites/test_suite_chachapoly.data b/tests/suites/test_suite_chachapoly.data index 1c4c670953..4dcac536c1 100644 --- a/tests/suites/test_suite_chachapoly.data +++ b/tests/suites/test_suite_chachapoly.data @@ -19,8 +19,8 @@ mbedtls_chachapoly_dec:"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc ChaCha20-Poly1305 State Flow chachapoly_state: -ChaCha20-Poly1305 overlong update preserves state -chachapoly_update_too_long_preserves_state: +ChaCha20-Poly1305 overlong generates the appropriate error +chachapoly_update_too_long: ChaCha20-Poly1305 Selftest depends_on:MBEDTLS_SELF_TEST diff --git a/tests/suites/test_suite_chachapoly.function b/tests/suites/test_suite_chachapoly.function index 594eb09101..fb4288ffb4 100644 --- a/tests/suites/test_suite_chachapoly.function +++ b/tests/suites/test_suite_chachapoly.function @@ -77,13 +77,12 @@ exit: /* END_CASE */ /* BEGIN_CASE */ -void chachapoly_update_too_long_preserves_state() +void chachapoly_update_too_long() { unsigned char key[32] = { 0 }; unsigned char nonce[12] = { 0 }; unsigned char input[65] = { 0 }; unsigned char output[65] = { 0 }; - unsigned char aad[1] = { 0 }; mbedtls_chachapoly_context ctx; mbedtls_chachapoly_init(&ctx); @@ -98,8 +97,6 @@ void chachapoly_update_too_long_preserves_state() MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); TEST_EQUAL(ctx.ciphertext_len, 0); - TEST_EQUAL(mbedtls_chachapoly_update_aad(&ctx, aad, sizeof(aad)), 0); - exit: mbedtls_chachapoly_free(&ctx); } From e1641d00e24d08af4fea7c43534eeb86d0135811 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Thu, 4 Jun 2026 07:56:13 +0100 Subject: [PATCH 20/28] Remove assertion that ctx.ciphertext_len is 0 Signed-off-by: Ben Taylor (cherry picked from commit 3f3d3da789e05f078a4df9995278d3d53e273513) --- tests/suites/test_suite_chachapoly.function | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/suites/test_suite_chachapoly.function b/tests/suites/test_suite_chachapoly.function index fb4288ffb4..bdea57b8f2 100644 --- a/tests/suites/test_suite_chachapoly.function +++ b/tests/suites/test_suite_chachapoly.function @@ -95,7 +95,6 @@ void chachapoly_update_too_long() ctx.chacha20_ctx.state[12] = UINT32_MAX; TEST_EQUAL(mbedtls_chachapoly_update(&ctx, sizeof(input), input, output), MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); - TEST_EQUAL(ctx.ciphertext_len, 0); exit: mbedtls_chachapoly_free(&ctx); From 176bea08b71a6c609e8743d7f274e7fc963949be Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Mon, 8 Jun 2026 08:14:06 +0100 Subject: [PATCH 21/28] Correct maximum size for chacha20 Signed-off-by: Ben Taylor (cherry picked from commit 540c75e5a0fa787b6e4ffdaec33648f83b850b6a) --- tests/suites/test_suite_psa_crypto.function | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/suites/test_suite_psa_crypto.function b/tests/suites/test_suite_psa_crypto.function index e408bf05fb..f9338a5a8e 100644 --- a/tests/suites/test_suite_psa_crypto.function +++ b/tests/suites/test_suite_psa_crypto.function @@ -6370,7 +6370,7 @@ void aead_multipart_state_test(int key_type_arg, data_t *key_data, if (operation.alg == PSA_ALG_GCM || operation.alg == PSA_ALG_CHACHA20_POLY1305) { TEST_EQUAL(psa_aead_set_lengths(&operation, additional_data->len, - SIZE_MAX), + 0xfffffffe * 64 + 1), PSA_ERROR_INVALID_ARGUMENT); TEST_EQUAL(psa_aead_set_nonce(&operation, nonce->x, nonce->len), PSA_ERROR_BAD_STATE); From 77a2831999a89e14f9f8973f478d9f7bc8c51c00 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Mon, 8 Jun 2026 08:17:43 +0100 Subject: [PATCH 22/28] Remove length checking from chacha20 Signed-off-by: Ben Taylor (cherry picked from commit 21594d5f6daeb8b6f633d481d3335bd7399f8237) --- tests/suites/test_suite_psa_crypto.function | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/suites/test_suite_psa_crypto.function b/tests/suites/test_suite_psa_crypto.function index f9338a5a8e..312c36b172 100644 --- a/tests/suites/test_suite_psa_crypto.function +++ b/tests/suites/test_suite_psa_crypto.function @@ -6367,8 +6367,7 @@ void aead_multipart_state_test(int key_type_arg, data_t *key_data, #if SIZE_MAX > UINT32_MAX PSA_ASSERT(psa_aead_encrypt_setup(&operation, key, alg)); - if (operation.alg == PSA_ALG_GCM || - operation.alg == PSA_ALG_CHACHA20_POLY1305) { + if (operation.alg == PSA_ALG_GCM) { TEST_EQUAL(psa_aead_set_lengths(&operation, additional_data->len, 0xfffffffe * 64 + 1), PSA_ERROR_INVALID_ARGUMENT); From 89005658bcdc5bf9ad26b8ac677f075c49fadd63 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Mon, 8 Jun 2026 08:20:35 +0100 Subject: [PATCH 23/28] Remove issue number and add CVE to ChangeLog Signed-off-by: Ben Taylor (cherry picked from commit 84bb99ca557ddfe4864d3b9ae84299ba4fb0c358) --- ChangeLog.d/chacha20-counter-overflow.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.d/chacha20-counter-overflow.txt b/ChangeLog.d/chacha20-counter-overflow.txt index 10ce71f081..0d824d6a9c 100644 --- a/ChangeLog.d/chacha20-counter-overflow.txt +++ b/ChangeLog.d/chacha20-counter-overflow.txt @@ -1,4 +1,4 @@ Security * Reject ChaCha20 operations that would make the 32-bit block counter wrap around, which could otherwise reuse keystream and compromise - confidentiality. Fixes #1604. + confidentiality. CVE-2026-50584 From 29090200c2c168fc0b1f1b8ec2aa078bc26b2a90 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Mon, 8 Jun 2026 14:03:23 +0100 Subject: [PATCH 24/28] Update tests to reflect change to lenght checking Signed-off-by: Ben Taylor (cherry picked from commit f650b3f761ff5d88c94800d168b1889567106eec) --- tests/suites/test_suite_psa_crypto.function | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/suites/test_suite_psa_crypto.function b/tests/suites/test_suite_psa_crypto.function index 312c36b172..acdad52970 100644 --- a/tests/suites/test_suite_psa_crypto.function +++ b/tests/suites/test_suite_psa_crypto.function @@ -6369,7 +6369,13 @@ void aead_multipart_state_test(int key_type_arg, data_t *key_data, if (operation.alg == PSA_ALG_GCM) { TEST_EQUAL(psa_aead_set_lengths(&operation, additional_data->len, - 0xfffffffe * 64 + 1), + (size_t) 0xfffffffe * 64 + 1), + PSA_ERROR_INVALID_ARGUMENT); + TEST_EQUAL(psa_aead_set_nonce(&operation, nonce->x, nonce->len), + PSA_ERROR_BAD_STATE); + } else if (operation.alg == PSA_ALG_CHACHA20_POLY1305) { + TEST_EQUAL(psa_aead_set_lengths(&operation, additional_data->len, + SIZE_MAX), PSA_ERROR_INVALID_ARGUMENT); TEST_EQUAL(psa_aead_set_nonce(&operation, nonce->x, nonce->len), PSA_ERROR_BAD_STATE); From bac83ee5ded09b9fd9d2c245fbedbb74de32f0a4 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Tue, 9 Jun 2026 08:32:20 +0100 Subject: [PATCH 25/28] Add additional verification that output hasn't been modified Signed-off-by: Ben Taylor (cherry picked from commit 720ee2392a6ced5ee0220a87c20d6c3d43a10650) --- tests/suites/test_suite_chachapoly.function | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/suites/test_suite_chachapoly.function b/tests/suites/test_suite_chachapoly.function index bdea57b8f2..670160b2fd 100644 --- a/tests/suites/test_suite_chachapoly.function +++ b/tests/suites/test_suite_chachapoly.function @@ -84,6 +84,7 @@ void chachapoly_update_too_long() unsigned char input[65] = { 0 }; unsigned char output[65] = { 0 }; mbedtls_chachapoly_context ctx; + size_t i; mbedtls_chachapoly_init(&ctx); @@ -93,8 +94,12 @@ void chachapoly_update_too_long() /* Force the next update to be too long without processing a huge buffer. */ ctx.chacha20_ctx.state[12] = UINT32_MAX; + memset(output, 0x2a, sizeof(output)); TEST_EQUAL(mbedtls_chachapoly_update(&ctx, sizeof(input), input, output), MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); + for (i = 0; i < sizeof(output); i++) { + TEST_EQUAL(output[i], 0x2a); + } exit: mbedtls_chachapoly_free(&ctx); From 4ee90fe0e626c5d7ed06f803ff3a4470172659ac Mon Sep 17 00:00:00 2001 From: Ben Taylor <32939606+bjwtaylor@users.noreply.github.com> Date: Wed, 10 Jun 2026 07:48:55 +0100 Subject: [PATCH 26/28] Correct logic for keystream_bytes_used Co-authored-by: minosgalanakis <30719586+minosgalanakis@users.noreply.github.com> Signed-off-by: Ben Taylor <32939606+bjwtaylor@users.noreply.github.com> (cherry picked from commit 8de9f6aa776d5e92d77b10dfca6666f83f542ab2) --- library/chacha20.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/chacha20.c b/library/chacha20.c index 53bf4900b6..41383c44b2 100644 --- a/library/chacha20.c +++ b/library/chacha20.c @@ -210,7 +210,7 @@ int mbedtls_chacha20_check_counter_wrap(const mbedtls_chacha20_context *ctx, size_t available_keystream = 0; uint64_t needed_blocks = 0; - if (ctx->keystream_bytes_used == CHACHA20_COUNTER_EXHAUSTED) { + if (ctx->keystream_bytes_used >= CHACHA20_COUNTER_EXHAUSTED) { return size == 0U ? 0 : MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA; } From 757e75732d7e6285d0b8f360e3897c6b9a63e5ef Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Thu, 11 Jun 2026 07:51:42 +0100 Subject: [PATCH 27/28] Add note clarifying that output integrity for the chachpoly update is currently provided, though not gauranteed in the future Signed-off-by: Ben Taylor (cherry picked from commit b70d73e6a7591f99a05e8b1c03f0b3915ae7cdff) --- tests/suites/test_suite_chachapoly.function | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/suites/test_suite_chachapoly.function b/tests/suites/test_suite_chachapoly.function index 670160b2fd..294faa1fcf 100644 --- a/tests/suites/test_suite_chachapoly.function +++ b/tests/suites/test_suite_chachapoly.function @@ -94,6 +94,9 @@ void chachapoly_update_too_long() /* Force the next update to be too long without processing a huge buffer. */ ctx.chacha20_ctx.state[12] = UINT32_MAX; + /* Test that the failed update does not modify the output. This is a + * defense in depth assertion, however is not currently gauranteed by + * the API */ memset(output, 0x2a, sizeof(output)); TEST_EQUAL(mbedtls_chachapoly_update(&ctx, sizeof(input), input, output), MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA); From e8908dfa54422bed618ed6dc3fb9a8a77d516245 Mon Sep 17 00:00:00 2001 From: Ben Taylor Date: Thu, 11 Jun 2026 08:35:27 +0100 Subject: [PATCH 28/28] Correct maximum lengths in length tests Signed-off-by: Ben Taylor (cherry picked from commit 89620b3cd4649dc3af967af8cf901dcaa1986ec0) --- tests/suites/test_suite_psa_crypto.function | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/suites/test_suite_psa_crypto.function b/tests/suites/test_suite_psa_crypto.function index acdad52970..612faadf82 100644 --- a/tests/suites/test_suite_psa_crypto.function +++ b/tests/suites/test_suite_psa_crypto.function @@ -6363,19 +6363,20 @@ void aead_multipart_state_test(int key_type_arg, data_t *key_data, psa_aead_abort(&operation); - /* Test for setting nonce after calling set lengths with plaintext length of SIZE_MAX */ + /* Test for setting nonce after calling set lengths with plaintext length above the + * algorithm limit. */ #if SIZE_MAX > UINT32_MAX PSA_ASSERT(psa_aead_encrypt_setup(&operation, key, alg)); if (operation.alg == PSA_ALG_GCM) { TEST_EQUAL(psa_aead_set_lengths(&operation, additional_data->len, - (size_t) 0xfffffffe * 64 + 1), + (size_t) 0xfffffffe * 16 + 1), PSA_ERROR_INVALID_ARGUMENT); TEST_EQUAL(psa_aead_set_nonce(&operation, nonce->x, nonce->len), PSA_ERROR_BAD_STATE); } else if (operation.alg == PSA_ALG_CHACHA20_POLY1305) { TEST_EQUAL(psa_aead_set_lengths(&operation, additional_data->len, - SIZE_MAX), + (size_t) UINT32_MAX * 64 + 1), PSA_ERROR_INVALID_ARGUMENT); TEST_EQUAL(psa_aead_set_nonce(&operation, nonce->x, nonce->len), PSA_ERROR_BAD_STATE); @@ -6388,16 +6389,20 @@ void aead_multipart_state_test(int key_type_arg, data_t *key_data, psa_aead_abort(&operation); #endif - /* Test for calling set lengths with a plaintext length of SIZE_MAX, after setting nonce */ + /* Test for calling set lengths with a plaintext length above the algorithm limit, + * after setting nonce. */ #if SIZE_MAX > UINT32_MAX PSA_ASSERT(psa_aead_encrypt_setup(&operation, key, alg)); PSA_ASSERT(psa_aead_set_nonce(&operation, nonce->x, nonce->len)); - if (operation.alg == PSA_ALG_GCM || - operation.alg == PSA_ALG_CHACHA20_POLY1305) { + if (operation.alg == PSA_ALG_GCM) { TEST_EQUAL(psa_aead_set_lengths(&operation, additional_data->len, - SIZE_MAX), + (size_t) 0xfffffffe * 16 + 1), + PSA_ERROR_INVALID_ARGUMENT); + } else if (operation.alg == PSA_ALG_CHACHA20_POLY1305) { + TEST_EQUAL(psa_aead_set_lengths(&operation, additional_data->len, + (size_t) UINT32_MAX * 64 + 1), PSA_ERROR_INVALID_ARGUMENT); } else if (operation.alg != PSA_ALG_CCM) { PSA_ASSERT(psa_aead_set_lengths(&operation, additional_data->len,