From 13f5c4bda271d896c6ea4d1b1d2e7dfa046c9694 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Tue, 1 Sep 2026 19:32:38 +0100 Subject: [PATCH] ext/hash: Fix buffer overflow in hash_pbkdf2() with a large output length hash_pbkdf2() sized the digest buffer with ceil((float) length / 2.0) and the block count with ceil((float) digest_length / (float) ops->digest_size). A float carries 24 bits of mantissa, so past 2^24 the conversion rounds and both counts come out wrong: rounding up makes zend_bin2hex() write past the end of the return string, rounding down leaves the tail of the string uninitialized. hash_pbkdf2('md5', 'password', 'salt', 1, 268435473) writes 8 bytes out of bounds under ASAN. Both counts are exact in integer arithmetic, so this replaces them with round-up divisions, which also avoids the signed overflow with an output length of PHP_INT_MAX. had no other user in the file. Close GH-23380 --- NEWS | 4 ++++ ext/hash/hash.c | 5 ++--- ext/hash/tests/hash_pbkdf2_large_length.phpt | 19 +++++++++++++++++++ ext/hash/tests/hash_pbkdf2_max_length.phpt | 17 +++++++++++++++++ .../tests/hash_pbkdf2_max_length_raw.phpt | 17 +++++++++++++++++ 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 ext/hash/tests/hash_pbkdf2_large_length.phpt create mode 100644 ext/hash/tests/hash_pbkdf2_max_length.phpt create mode 100644 ext/hash/tests/hash_pbkdf2_max_length_raw.phpt diff --git a/NEWS b/NEWS index ac08278a8a8b..84f5597d9780 100644 --- a/NEWS +++ b/NEWS @@ -31,6 +31,10 @@ PHP NEWS - FPM: . Fixed bug GH-19320 (FPM UID and GID overflow). (Pratik Bhujel) +- Hash: + . Fixed a buffer overflow in hash_pbkdf2() with a large output length. + (Lazizbek Ergashev) + - Intl: . Fixed grapheme_strpos() and grapheme_strrpos() with an empty needle returning UTF-16 offsets instead of grapheme offsets. (Ilia Alshanetsky) diff --git a/ext/hash/hash.c b/ext/hash/hash.c index 1c90f4821f1a..2f8cef87da37 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -19,7 +19,6 @@ #include #endif -#include #include "php_hash.h" #include "ext/standard/info.h" #include "ext/standard/file.h" @@ -1043,10 +1042,10 @@ PHP_FUNCTION(hash_pbkdf2) } digest_length = length; if (!raw_output) { - digest_length = (zend_long) ceil((float) length / 2.0); + digest_length = length / 2 + (length % 2); } - loops = (zend_long) ceil((float) digest_length / (float) ops->digest_size); + loops = (digest_length - 1) / ops->digest_size + 1; result = safe_emalloc(loops, ops->digest_size, 0); diff --git a/ext/hash/tests/hash_pbkdf2_large_length.phpt b/ext/hash/tests/hash_pbkdf2_large_length.phpt new file mode 100644 index 000000000000..527e238a2cc3 --- /dev/null +++ b/ext/hash/tests/hash_pbkdf2_large_length.phpt @@ -0,0 +1,19 @@ +--TEST-- +Hash: hash_pbkdf2() function : large output length +--FILE-- + +--EXPECT-- +int(33554433) +bool(true) diff --git a/ext/hash/tests/hash_pbkdf2_max_length.phpt b/ext/hash/tests/hash_pbkdf2_max_length.phpt new file mode 100644 index 000000000000..eccd8abaa2bf --- /dev/null +++ b/ext/hash/tests/hash_pbkdf2_max_length.phpt @@ -0,0 +1,17 @@ +--TEST-- +Hash: hash_pbkdf2() function : output length of PHP_INT_MAX +--SKIPIF-- + +--INI-- +memory_limit=128M +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of 134217728 bytes exhausted%s(tried to allocate 4611686018427387904 bytes) in %s on line %d diff --git a/ext/hash/tests/hash_pbkdf2_max_length_raw.phpt b/ext/hash/tests/hash_pbkdf2_max_length_raw.phpt new file mode 100644 index 000000000000..b0eceefa356a --- /dev/null +++ b/ext/hash/tests/hash_pbkdf2_max_length_raw.phpt @@ -0,0 +1,17 @@ +--TEST-- +Hash: hash_pbkdf2() function : raw output length of PHP_INT_MAX +--SKIPIF-- + +--INI-- +memory_limit=128M +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of 134217728 bytes exhausted%s(tried to allocate 9223372036854775808 bytes) in %s on line %d