From 6f6d04fadee9fdd6763f01cbbfc5ed2cef360b05 Mon Sep 17 00:00:00 2001 From: Martin Mirchev Date: Thu, 22 Oct 2026 23:41:48 +0800 Subject: [PATCH 1/2] 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. --- ext/hash/hash_gost.c | 13 ++++------- ext/hash/hash_snefru.c | 13 ++++------- ext/hash/tests/gost_snefru_512mib.phpt | 30 ++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 18 deletions(-) create mode 100644 ext/hash/tests/gost_snefru_512mib.phpt diff --git a/ext/hash/hash_gost.c b/ext/hash/hash_gost.c index ee2f3c89381c..8f23e8dcc097 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..3b6ff10f3096 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..0304f2bfc9a5 --- /dev/null +++ b/ext/hash/tests/gost_snefru_512mib.phpt @@ -0,0 +1,30 @@ +--TEST-- +Hash: gost and snefru with 512 MiB of input (bit counter carry) +--SKIPIF-- + +--INI-- +memory_limit=-1 +--FILE-- + +--EXPECT-- +gost: 6c30acc180f544a79c22fa763593a812aec721dd61658b1a5e9a6f3a7955146d +snefru: 0842a8e735ab08a30149e7e6e34833f365291978ee68c37a5135253b70afa486 +snefru: 0842a8e735ab08a30149e7e6e34833f365291978ee68c37a5135253b70afa486 From e2c8c6554992f67448973dcbf461e6a092afab9a Mon Sep 17 00:00:00 2001 From: Martin Mirchev Date: Sat, 24 Oct 2026 09:37:25 +0800 Subject: [PATCH 2/2] Address review: combine the counter words with OR, run the test on 32-bit Use a bitwise OR to combine the two 32-bit words of the bit counter so that compilers emit a single 64-bit addition, and drop the 64-bit-only SKIPIF from the resource-heavy test. --- ext/hash/hash_gost.c | 2 +- ext/hash/hash_snefru.c | 2 +- ext/hash/tests/gost_snefru_512mib.phpt | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ext/hash/hash_gost.c b/ext/hash/hash_gost.c index 8f23e8dcc097..56ef9bfaa67d 100644 --- a/ext/hash/hash_gost.c +++ b/ext/hash/hash_gost.c @@ -249,7 +249,7 @@ PHP_HASH_API void PHP_GOSTInitCrypto(PHP_GOST_CTX *context, ZEND_ATTRIBUTE_UNUSE PHP_HASH_API void PHP_GOSTUpdate(PHP_GOST_CTX *context, const unsigned char *input, size_t len) { - uint64_t count = ((uint64_t) context->count[1] << 32) + context->count[0] + (uint64_t) 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); diff --git a/ext/hash/hash_snefru.c b/ext/hash/hash_snefru.c index 3b6ff10f3096..98d4601d14e2 100644 --- a/ext/hash/hash_snefru.c +++ b/ext/hash/hash_snefru.c @@ -135,7 +135,7 @@ PHP_HASH_API void PHP_SNEFRUInit(PHP_SNEFRU_CTX *context, ZEND_ATTRIBUTE_UNUSED PHP_HASH_API void PHP_SNEFRUUpdate(PHP_SNEFRU_CTX *context, const unsigned char *input, size_t len) { - uint64_t count = ((uint64_t) context->count[0] << 32) + context->count[1] + (uint64_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; diff --git a/ext/hash/tests/gost_snefru_512mib.phpt b/ext/hash/tests/gost_snefru_512mib.phpt index 0304f2bfc9a5..6649140b2722 100644 --- a/ext/hash/tests/gost_snefru_512mib.phpt +++ b/ext/hash/tests/gost_snefru_512mib.phpt @@ -3,7 +3,6 @@ Hash: gost and snefru with 512 MiB of input (bit counter carry) --SKIPIF-- --INI--