From 33a1fa3a2c87cd50646490dcb1266fbc9897185e Mon Sep 17 00:00:00 2001 From: Johnson Ajibi Date: Sun, 27 Sep 2026 09:49:25 +0100 Subject: [PATCH 1/3] sha256: fold high bits of ilen into total[1] on 64-bit platforms mbedtls_sha256_update() tracks the running byte count in a 64-bit counter split across ctx->total[0]/[1] (32 bits each), used at finalization to encode the message length into the padding. ctx->total[0] += (uint32_t) ilen truncates ilen to its low 32 bits before adding, and the overflow check right after (ctx->total[0] < (uint32_t) ilen) only detects the single wraparound of that 32-bit addition. Neither step ever looks at bits 32 and up of ilen itself. So when a single update() call is passed >= 2^32 bytes on a 64-bit platform (size_t wider than 32 bits), every byte is correctly hashed into the message, but the length folded into the final padding block is wrong by a multiple of 2^32 bytes, producing an incorrect digest despite no error being returned. Fold ilen>>32 into total[1] directly, guarded on SIZE_MAX so 32-bit platforms (where ilen cannot exceed 2^32-1 and the shift would be UB-adjacent) are unaffected. Verified live: generated a 4GiB+64 byte random file, hashed it with mbedtls_sha256_update() in a single call both before and after this fix, and compared against sha256sum of the same file. - before: 803ab176fbafaf3cf345cbe22e2a35715c3ae8f7fbf241a00677d4d733db0214 (wrong) - after: 21a1f671cd9797227f341a243fed82c8fe95a769a58a6f3965e958f047e969d6 (matches sha256sum) Also re-verified normal-sized inputs (empty string, "abc") against known SHA-256 vectors, and that a two-call split of a 100-byte input still agrees with a single whole-buffer call. Fixes #10964. Signed-off-by: Johnson Ajibi --- ChangeLog.d/sha256-update-ilen-over-4gib.txt | 6 ++++++ library/sha256.c | 4 ++++ 2 files changed, 10 insertions(+) create mode 100644 ChangeLog.d/sha256-update-ilen-over-4gib.txt diff --git a/ChangeLog.d/sha256-update-ilen-over-4gib.txt b/ChangeLog.d/sha256-update-ilen-over-4gib.txt new file mode 100644 index 0000000000..8769ea10ac --- /dev/null +++ b/ChangeLog.d/sha256-update-ilen-over-4gib.txt @@ -0,0 +1,6 @@ +Bugfix + * Fix mbedtls_sha256_update() computing the wrong digest when a single + call is passed 4GiB (2^32 bytes) or more of input on a 64-bit + platform: the upper 32 bits of the input length were silently + dropped from the running byte count used at finalization. Reported + by TimothyPMann in #10964. diff --git a/library/sha256.c b/library/sha256.c index 4f1ac3e0fb..06d2f53bc3 100644 --- a/library/sha256.c +++ b/library/sha256.c @@ -667,6 +667,10 @@ int mbedtls_sha256_update(mbedtls_sha256_context *ctx, ctx->total[1]++; } +#if SIZE_MAX > 0xFFFFFFFF + ctx->total[1] += (uint32_t) (ilen >> 32); +#endif + if (left && ilen >= fill) { memcpy((void *) (ctx->buffer + left), input, fill); From 23a2fa734b80c1973aa08c7cb4c79a8c49ffd95b Mon Sep 17 00:00:00 2001 From: Johnson Ajibi Date: Wed, 30 Sep 2026 22:19:04 +0100 Subject: [PATCH 2/3] md5/sha1/ripemd160: fix the same 4GiB ilen truncation as sha256 Per mpg's review, MD5, SHA-1 and RIPEMD-160 have the exact same bug as SHA-256: mbedtls_*_update() truncates ilen to its low 32 bits before adding it to ctx->total[0], and the overflow check right after only catches the single wraparound of that 32-bit addition -- neither step folds in bits 32 and up of ilen itself when a single update() call is passed >= 2^32 bytes on a 64-bit platform. SHA-512 does not have this bug (confirmed by TimothyPMann and independently by me): it tracks its 128-bit byte count in two uint64_t words, so ilen (a 64-bit size_t at most) can never overflow the low word before the carry-in step even runs. Applied the identical fix to all three: fold ilen >> 32 into total[1], guarded on SIZE_MAX so 32-bit platforms are unaffected. Verified live with the same method as the SHA-256 fix: hashed a real 4GiB + 64 byte file with mbedtls_md5_update()/sha1_update()/ ripemd160_update() in a single call and compared against md5sum(1), sha1sum(1), and `openssl dgst -rmd160`. algorithm before fix (wrong) after fix (matches reference tool) MD5 0e8b3fdad1980e2c4e66ef714abc3f0c 8b647773014d9f640da9887274f444cd SHA-1 b190a761fceaafdc19b62cbcc729ec22b02e7d0c 703fe5b4441eb203af67ed4fbc20263d44bc5f5c RIPEMD-160 68956d1517f2ebcef4761d2c0fddf11331a1e6b6 d14d7e80c32ddc603c677151bfd59fd8cf867c42 Also reworded the ChangeLog entry per feedback to cover all four functions concisely instead of describing the SHA-256 internals in detail. Signed-off-by: Johnson Ajibi --- ChangeLog.d/sha256-update-ilen-over-4gib.txt | 9 ++++----- library/md5.c | 4 ++++ library/ripemd160.c | 4 ++++ library/sha1.c | 4 ++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ChangeLog.d/sha256-update-ilen-over-4gib.txt b/ChangeLog.d/sha256-update-ilen-over-4gib.txt index 8769ea10ac..cad065a107 100644 --- a/ChangeLog.d/sha256-update-ilen-over-4gib.txt +++ b/ChangeLog.d/sha256-update-ilen-over-4gib.txt @@ -1,6 +1,5 @@ Bugfix - * Fix mbedtls_sha256_update() computing the wrong digest when a single - call is passed 4GiB (2^32 bytes) or more of input on a 64-bit - platform: the upper 32 bits of the input length were silently - dropped from the running byte count used at finalization. Reported - by TimothyPMann in #10964. + * Fix mbedtls_md5_update(), mbedtls_sha1_update(), + mbedtls_sha256_update() and mbedtls_ripemd160_update() computing the + wrong digest when a single call is passed 4GiB or more of input on a + 64-bit platform. Reported by TimothyPMann in #10964. diff --git a/library/md5.c b/library/md5.c index e4a87a2e09..088061930c 100644 --- a/library/md5.c +++ b/library/md5.c @@ -222,6 +222,10 @@ int mbedtls_md5_update(mbedtls_md5_context *ctx, ctx->total[1]++; } +#if SIZE_MAX > 0xFFFFFFFF + ctx->total[1] += (uint32_t) (ilen >> 32); +#endif + if (left && ilen >= fill) { memcpy((void *) (ctx->buffer + left), input, fill); if ((ret = mbedtls_internal_md5_process(ctx, ctx->buffer)) != 0) { diff --git a/library/ripemd160.c b/library/ripemd160.c index b4fc3cdba1..6a8f3123d3 100644 --- a/library/ripemd160.c +++ b/library/ripemd160.c @@ -285,6 +285,10 @@ int mbedtls_ripemd160_update(mbedtls_ripemd160_context *ctx, ctx->total[1]++; } +#if SIZE_MAX > 0xFFFFFFFF + ctx->total[1] += (uint32_t) (ilen >> 32); +#endif + if (left && ilen >= fill) { memcpy((void *) (ctx->buffer + left), input, fill); diff --git a/library/sha1.c b/library/sha1.c index dfbe481f39..fe28022ae2 100644 --- a/library/sha1.c +++ b/library/sha1.c @@ -257,6 +257,10 @@ int mbedtls_sha1_update(mbedtls_sha1_context *ctx, ctx->total[1]++; } +#if SIZE_MAX > 0xFFFFFFFF + ctx->total[1] += (uint32_t) (ilen >> 32); +#endif + if (left && ilen >= fill) { memcpy((void *) (ctx->buffer + left), input, fill); From 320fd18555255dae4c5ac4fd338da0769b86464b Mon Sep 17 00:00:00 2001 From: Johnson Ajibi Date: Fri, 2 Oct 2026 22:03:28 +0100 Subject: [PATCH 3/3] changelog: list every affected API explicitly per review feedback Previous wording only named the four low-level update() functions directly touched by the fix. mpg pointed out a normal user calling mbedtls_md_update(), psa_hash_compare(), or any of the one-shot wrappers (mbedtls_md5(), mbedtls_sha256(), psa_hash_compute(), etc.) has no way to tell from that wording whether they are affected, and that SHA-224 should be listed explicitly even though it shares mbedtls_sha256_update() under the hood, since that implementation detail should not be something a ChangeLog reader needs to already know. Signed-off-by: Johnson Ajibi --- ChangeLog.d/sha256-update-ilen-over-4gib.txt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ChangeLog.d/sha256-update-ilen-over-4gib.txt b/ChangeLog.d/sha256-update-ilen-over-4gib.txt index cad065a107..efbb7b4dc5 100644 --- a/ChangeLog.d/sha256-update-ilen-over-4gib.txt +++ b/ChangeLog.d/sha256-update-ilen-over-4gib.txt @@ -1,5 +1,12 @@ Bugfix - * Fix mbedtls_md5_update(), mbedtls_sha1_update(), - mbedtls_sha256_update() and mbedtls_ripemd160_update() computing the - wrong digest when a single call is passed 4GiB or more of input on a - 64-bit platform. Reported by TimothyPMann in #10964. + * Fix MD5, SHA1, SHA224, SHA256 and RIPEMD160 computing the wrong + digest when 4GiB of data or more are processed in a single call. + That can happen either when using the one-shot APIs + (psa_hash_compute(), psa_hash_compare(), mbedtls_md(), + mbedtls_md5(), mbedtls_sha1(), mbedtls_ripemd160(), + mbedtls_sha256()) with more than 4GiB of input, or when using the + streaming API and passing more than 4GiB in a single call to the + update function (psa_hash_update(), mbedtls_md_update(), + mbedtls_md5_update(), mbedtls_sha1_update(), + mbedtls_ripemd160_update(), mbedtls_sha256_update()). Reported by + TimothyPMann in #10964.