Merge pull request #1637 from bjwtaylor/chacha20-counter-overflow-restricted-3.6

Chacha20 counter overflow restricted 3.6
This commit is contained in:
Gilles Peskine
2026-06-22 12:49:53 +02:00
committed by GitHub
13 changed files with 242 additions and 10 deletions
@@ -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. CVE-2026-50584
+8 -1
View File
@@ -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;
@@ -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],
+9
View File
@@ -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,
+14 -3
View File
@@ -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
@@ -2470,6 +2478,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.
@@ -2618,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
+67 -1
View File
@@ -20,6 +20,8 @@
#include <stddef.h>
#include <string.h>
#include "chacha20_internal.h"
#include "mbedtls/platform.h"
#if !defined(MBEDTLS_CHACHA20_ALT)
@@ -31,6 +33,10 @@
#define CHACHA20_BLOCK_SIZE_BYTES (4U * 16U)
#define CHACHA20_MAX_BLOCKS UINT32_MAX
#define CHACHA20_COUNTER_EXHAUSTED (CHACHA20_BLOCK_SIZE_BYTES + 1U)
/**
* \brief ChaCha20 quarter round operation.
*
@@ -183,7 +189,7 @@ int mbedtls_chacha20_starts(mbedtls_chacha20_context *ctx,
uint32_t counter)
{
/* Counter */
ctx->state[12] = counter;
ctx->state[CHACHA20_CTR_INDEX] = counter;
/* Nonce */
ctx->state[13] = MBEDTLS_GET_UINT32_LE(nonce, 0);
@@ -198,12 +204,63 @@ int mbedtls_chacha20_starts(mbedtls_chacha20_context *ctx,
return 0;
}
int mbedtls_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 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;
if (size <= available_keystream) {
return 0;
}
if (ctx->state[CHACHA20_CTR_INDEX] == 0U) {
return MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA;
}
size -= available_keystream;
}
needed_blocks = (size / CHACHA20_BLOCK_SIZE_BYTES);
if (size % CHACHA20_BLOCK_SIZE_BYTES != 0U) {
needed_blocks++;
}
if (needed_blocks >
(uint64_t) CHACHA20_MAX_BLOCKS + 1U - ctx->state[CHACHA20_CTR_INDEX]) {
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 = mbedtls_chacha20_check_counter_wrap(ctx, size);
if (ret != 0) {
return ret;
}
/* Use leftover keystream bytes, if available */
while (size > 0U && ctx->keystream_bytes_used < CHACHA20_BLOCK_SIZE_BYTES) {
@@ -213,6 +270,11 @@ 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 */
@@ -225,6 +287,10 @@ int mbedtls_chacha20_update(mbedtls_chacha20_context *ctx,
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 */
+23
View File
@@ -0,0 +1,23 @@
/**
* \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"
#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 */
+9
View File
@@ -17,6 +17,8 @@
#include <string.h>
#include "chacha20_internal.h"
#include "mbedtls/platform.h"
#if !defined(MBEDTLS_CHACHAPOLY_ALT)
@@ -165,6 +167,13 @@ 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;
+7 -1
View File
@@ -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:
+3
View File
@@ -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:
+46
View File
@@ -67,6 +67,52 @@ 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, 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),
MBEDTLS_ERR_CHACHA20_BAD_INPUT_DATA);
/* 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);
mbedtls_chacha20_free(&ctx);
}
/* END_CASE */
/* BEGIN_CASE depends_on:MBEDTLS_SELF_TEST */
void chacha20_self_test()
{
+3
View File
@@ -19,6 +19,9 @@ mbedtls_chachapoly_dec:"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc
ChaCha20-Poly1305 State Flow
chachapoly_state:
ChaCha20-Poly1305 overlong generates the appropriate error
chachapoly_update_too_long:
ChaCha20-Poly1305 Selftest
depends_on:MBEDTLS_SELF_TEST
chachapoly_selftest:
@@ -76,6 +76,39 @@ exit:
}
/* END_CASE */
/* BEGIN_CASE */
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 };
mbedtls_chachapoly_context ctx;
size_t i;
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 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);
for (i = 0; i < sizeof(output); i++) {
TEST_EQUAL(output[i], 0x2a);
}
exit:
mbedtls_chachapoly_free(&ctx);
}
/* END_CASE */
/* BEGIN_CASE */
void chachapoly_state()
{
+16 -4
View File
@@ -6363,13 +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_MAX),
(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_t) UINT32_MAX * 64 + 1),
PSA_ERROR_INVALID_ARGUMENT);
TEST_EQUAL(psa_aead_set_nonce(&operation, nonce->x, nonce->len),
PSA_ERROR_BAD_STATE);
@@ -6382,7 +6389,8 @@ 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));
@@ -6390,7 +6398,11 @@ 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,
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,