Commit 89f4d82157e for php
commit 89f4d82157ead3bc0a33988c631e0e90bc533b3e
Author: Martin Mirchev <mmirchev@comp.nus.edu.sg>
Date: Thu Oct 22 23:41:48 2026 +0800
Fix GOST and Snefru digests of 512 MiB or more
The bit counter update in PHP_GOSTUpdate() and PHP_SNEFRUUpdate() set the
low word one too high when it wrapped, and added at most one carry to the
high word per update. Snefru also computed the bit length of a single
update in 32 bits. The encoded message length, and thus the digest, was
wrong for inputs of 2^29 bytes or more. Compute the counter with 64-bit
arithmetic.
Closes GH-24555.
diff --git a/NEWS b/NEWS
index 6d09ce4a22c..041d775722e 100644
--- a/NEWS
+++ b/NEWS
@@ -57,6 +57,9 @@ PHP NEWS
- FTP:
. Fix double free in ext/ftp data_accept() on accept failure. (alepacheco)
+- Hash:
+ . Fix GOST and Snefru digests of 512 MiB or more. (Martin Mirchev)
+
- MBString:
. Fixed bug GH-23958 (truncated encoding names resolving to full names). (Kamil Tekiela)
. Fixed out-of-bounds read in php_mb_rfc1867_getword() on a multibyte lead
diff --git a/ext/hash/hash_gost.c b/ext/hash/hash_gost.c
index ee2f3c89381..56ef9bfaa67 100644
--- a/ext/hash/hash_gost.c
+++ b/ext/hash/hash_gost.c
@@ -247,17 +247,12 @@ PHP_HASH_API void PHP_GOSTInitCrypto(PHP_GOST_CTX *context, ZEND_ATTRIBUTE_UNUSE
context->tables = &tables_crypto;
}
-static const uint32_t MAX32 = 0xffffffffLU;
-
PHP_HASH_API void PHP_GOSTUpdate(PHP_GOST_CTX *context, const unsigned char *input, size_t len)
{
- if ((MAX32 - context->count[0]) < (len * 8)) {
- context->count[1]++;
- context->count[0] = MAX32 - context->count[0];
- context->count[0] = (len * 8) - context->count[0];
- } else {
- context->count[0] += len * 8;
- }
+ uint64_t count = (((uint64_t) context->count[1] << 32) | context->count[0]) + (uint64_t) len * 8;
+
+ context->count[0] = (uint32_t) count;
+ context->count[1] = (uint32_t) (count >> 32);
if (context->length + len < 32) {
memcpy(&context->buffer[context->length], input, len);
diff --git a/ext/hash/hash_snefru.c b/ext/hash/hash_snefru.c
index fd2ee28c80e..98d4601d14e 100644
--- a/ext/hash/hash_snefru.c
+++ b/ext/hash/hash_snefru.c
@@ -133,17 +133,12 @@ PHP_HASH_API void PHP_SNEFRUInit(PHP_SNEFRU_CTX *context, ZEND_ATTRIBUTE_UNUSED
memset(context, 0, sizeof(*context));
}
-static const uint32_t MAX32 = 0xffffffffLU;
-
PHP_HASH_API void PHP_SNEFRUUpdate(PHP_SNEFRU_CTX *context, const unsigned char *input, size_t len)
{
- if ((MAX32 - context->count[1]) < (len * 8)) {
- context->count[0]++;
- context->count[1] = MAX32 - context->count[1];
- context->count[1] = ((uint32_t) len * 8) - context->count[1];
- } else {
- context->count[1] += (uint32_t) len * 8;
- }
+ uint64_t count = (((uint64_t) context->count[0] << 32) | context->count[1]) + (uint64_t) len * 8;
+
+ context->count[0] = (uint32_t) (count >> 32);
+ context->count[1] = (uint32_t) count;
if (context->length + len < 32) {
memcpy(&context->buffer[context->length], input, len);
diff --git a/ext/hash/tests/gost_snefru_512mib.phpt b/ext/hash/tests/gost_snefru_512mib.phpt
new file mode 100644
index 00000000000..6649140b272
--- /dev/null
+++ b/ext/hash/tests/gost_snefru_512mib.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Hash: gost and snefru with 512 MiB of input (bit counter carry)
+--SKIPIF--
+<?php
+if (!getenv('RUN_RESOURCE_HEAVY_TESTS')) die('skip resource-heavy test');
+if (getenv('SKIP_SLOW_TESTS')) die('skip slow tests excluded by request');
+?>
+--INI--
+memory_limit=-1
+--FILE--
+<?php
+/* 2^29 bytes are 2^32 bits, so the low word of the bit counter wraps. */
+$chunk = str_repeat('a', 1 << 20);
+foreach (['gost', 'snefru'] as $algo) {
+ $ctx = hash_init($algo);
+ for ($i = 0; $i < 512; $i++) {
+ hash_update($ctx, $chunk);
+ }
+ echo $algo, ': ', hash_final($ctx), "\n";
+}
+unset($chunk);
+
+/* A single update of 2^29 bytes. */
+echo 'snefru: ', hash('snefru', str_repeat('a', 1 << 29)), "\n";
+?>
+--EXPECT--
+gost: 6c30acc180f544a79c22fa763593a812aec721dd61658b1a5e9a6f3a7955146d
+snefru: 0842a8e735ab08a30149e7e6e34833f365291978ee68c37a5135253b70afa486
+snefru: 0842a8e735ab08a30149e7e6e34833f365291978ee68c37a5135253b70afa486