[php-src] PHP-8.6: Fix GOST and Snefru digests of 512 MiB or more
| From: | Martin Mirchev via ndossche | Date: | Sat, 24 Oct 2026 08:55:59 +0000 |
| Subject: | [php-src] PHP-8.6: Fix GOST and Snefru digests of 512 MiB or more | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142658@lists.php.net to get a copy of this message | ||
Author: Martin Mirchev (Marti2203)
Committer: ndossche (ndossche)
Date: 2026-10-24T10:54:51+02:00
Commit: https://github.com/php/php-src/commit/89f4d82157ead3bc0a33988c631e0e90bc533b3e
Raw diff: https://github.com/php/php-src/commit/89f4d82157ead3bc0a33988c631e0e90bc533b3e.diff
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.
Changed paths:
A ext/hash/tests/gost_snefru_512mib.phpt
M NEWS
M ext/hash/hash_gost.c
M ext/hash/hash_snefru.c
Diff:
diff --git a/NEWS b/NEWS
index 6d09ce4a22cc..041d775722e3 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 ee2f3c89381c..56ef9bfaa67d 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 fd2ee28c80e7..98d4601d14e2 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 000000000000..6649140b2722
--- /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