Commit 13f5c4bda27 for php.net
commit 13f5c4bda271d896c6ea4d1b1d2e7dfa046c9694
Author: Lazizbek Ergashev <lazerg2@gmail.com>
Date: Tue Sep 1 19:32:38 2026 +0100
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. <math.h> had no other user in the file.
Close GH-23380
diff --git a/NEWS b/NEWS
index ac08278a8a8..84f5597d978 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 1c90f4821f1..2f8cef87da3 100644
--- a/ext/hash/hash.c
+++ b/ext/hash/hash.c
@@ -19,7 +19,6 @@
#include <config.h>
#endif
-#include <math.h>
#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 00000000000..527e238a2cc
--- /dev/null
+++ b/ext/hash/tests/hash_pbkdf2_large_length.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Hash: hash_pbkdf2() function : large output length
+--FILE--
+<?php
+
+$length = 33554433;
+$hash = hash_pbkdf2('md5', 'password', 'salt', 1, $length);
+
+/* The last hexit comes from the first byte of the final PBKDF2 block. */
+$block = intdiv(intdiv($length + 1, 2) - 1, 16) + 1;
+$expected = bin2hex(hash_hmac('md5', 'salt' . pack('N', $block), 'password', true));
+
+var_dump(strlen($hash));
+var_dump($hash[$length - 1] === $expected[0]);
+
+?>
+--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 00000000000..eccd8abaa2b
--- /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--
+<?php
+if (PHP_INT_SIZE == 4) die("skip this test is not for 32bit platforms");
+if (getenv("USE_ZEND_ALLOC") === "0") die("skip Zend MM disabled");
+?>
+--INI--
+memory_limit=128M
+--FILE--
+<?php
+
+hash_pbkdf2('md5', 'password', 'salt', 1, PHP_INT_MAX);
+
+?>
+--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 00000000000..b0eceefa356
--- /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--
+<?php
+if (PHP_INT_SIZE == 4) die("skip this test is not for 32bit platforms");
+if (getenv("USE_ZEND_ALLOC") === "0") die("skip Zend MM disabled");
+?>
+--INI--
+memory_limit=128M
+--FILE--
+<?php
+
+hash_pbkdf2('md5', 'password', 'salt', 1, PHP_INT_MAX, true);
+
+?>
+--EXPECTF--
+Fatal error: Allowed memory size of 134217728 bytes exhausted%s(tried to allocate 9223372036854775808 bytes) in %s on line %d