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