Commit 0fb3d658dc3 for php.net
commit 0fb3d658dc3264d3288353a641cce2c6adeba1bc
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Fri Jun 26 06:42:53 2026 -0400
ext/sodium: throw ValueError for pwhash argument errors (#22388)
The four password-hashing functions reported out-of-range arguments (a
non-positive or below-minimum opslimit or memlimit, an oversized hash
length or password, a wrong-length salt) as a SodiumException. These are
argument-value errors, so throw ValueError via zend_argument_value_error()
instead, matching the rest of the engine. SodiumException is still used
for internal libsodium failures.
SodiumException's create_object empties the whole backtrace, which also
protects caller frames holding the password; a plain ValueError does not,
so each converted site keeps an explicit
sodium_remove_param_values_from_backtrace(EG(exception)), mirroring the
ZPP-failure paths.
diff --git a/NEWS b/NEWS
index f13f2e00ca4..9d38664136a 100644
--- a/NEWS
+++ b/NEWS
@@ -230,6 +230,8 @@ PHP NEWS
- Sodium:
. Added support for libsodium 1.0.21 IPcrypt and XOF APIs. (jedisct1)
+ . pwhash argument-validation errors now throw ValueError instead of
+ SodiumException. (iliaal)
- SPL:
. DirectoryIterator key can now work better with filesystem supporting larger
diff --git a/UPGRADING b/UPGRADING
index ae0e3afc97a..58bf1f9c9a1 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -127,6 +127,15 @@ PHP 8.6 UPGRADE NOTES
occurrence constraints and integer restriction facets. Negative minOccurs
and maxOccurs values are rejected as well.
+- Sodium:
+ . The password-hashing functions sodium_crypto_pwhash(),
+ sodium_crypto_pwhash_str(),
+ sodium_crypto_pwhash_scryptsalsa208sha256() and
+ sodium_crypto_pwhash_scryptsalsa208sha256_str() now throw ValueError
+ instead of SodiumException when an argument is out of range, such as an
+ opslimit or memlimit below the documented minimum. SodiumException is
+ still thrown for internal libsodium failures.
+
- SPL:
. SplObjectStorage::getHash() implementations may no longer mutate any
SplObjectStorage instance. Attempting to do so now throws an Error.
diff --git a/ext/sodium/libsodium.c b/ext/sodium/libsodium.c
index bd246abb53a..8c85991150b 100644
--- a/ext/sodium/libsodium.c
+++ b/ext/sodium/libsodium.c
@@ -1429,23 +1429,28 @@ PHP_FUNCTION(sodium_crypto_pwhash)
RETURN_THROWS();
}
if (hash_len <= 0) {
- zend_argument_error(sodium_exception_ce, 1, "must be greater than 0");
+ zend_argument_value_error(1, "must be greater than 0");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (hash_len >= 0xffffffff) {
- zend_argument_error(sodium_exception_ce, 1, "is too large");
+ zend_argument_value_error(1, "must be less than 4294967295 bytes");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (passwd_len >= 0xffffffff) {
- zend_argument_error(sodium_exception_ce, 2, "is too long");
+ zend_argument_value_error(2, "must be less than 4294967295 bytes");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (opslimit <= 0) {
- zend_argument_error(sodium_exception_ce, 4, "must be greater than 0");
+ zend_argument_value_error(4, "must be greater than 0");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (memlimit <= 0 || memlimit > SIZE_MAX) {
- zend_argument_error(sodium_exception_ce, 5, "must be greater than 0");
+ zend_argument_value_error(5, "must be greater than 0");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (alg != crypto_pwhash_ALG_ARGON2I13
@@ -1460,15 +1465,18 @@ PHP_FUNCTION(sodium_crypto_pwhash)
zend_error(E_WARNING, "empty password");
}
if (salt_len != crypto_pwhash_SALTBYTES) {
- zend_argument_error(sodium_exception_ce, 3, "must be SODIUM_CRYPTO_PWHASH_SALTBYTES bytes long");
+ zend_argument_value_error(3, "must be SODIUM_CRYPTO_PWHASH_SALTBYTES bytes long");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (opslimit < crypto_pwhash_OPSLIMIT_MIN) {
- zend_argument_error(sodium_exception_ce, 4, "must be greater than or equal to %d", crypto_pwhash_OPSLIMIT_MIN);
+ zend_argument_value_error(4, "must be greater than or equal to %d", crypto_pwhash_OPSLIMIT_MIN);
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (memlimit < crypto_pwhash_MEMLIMIT_MIN) {
- zend_argument_error(sodium_exception_ce, 5, "must be greater than or equal to %d", crypto_pwhash_MEMLIMIT_MIN);
+ zend_argument_value_error(5, "must be greater than or equal to %d", crypto_pwhash_MEMLIMIT_MIN);
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
hash = zend_string_alloc((size_t) hash_len, 0);
@@ -1513,26 +1521,31 @@ PHP_FUNCTION(sodium_crypto_pwhash_str)
RETURN_THROWS();
}
if (opslimit <= 0) {
- zend_argument_error(sodium_exception_ce, 2, "must be greater than 0");
+ zend_argument_value_error(2, "must be greater than 0");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (memlimit <= 0 || memlimit > SIZE_MAX) {
- zend_argument_error(sodium_exception_ce, 3, "must be greater than 0");
+ zend_argument_value_error(3, "must be greater than 0");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (passwd_len >= 0xffffffff) {
- zend_argument_error(sodium_exception_ce, 1, "is too long");
+ zend_argument_value_error(1, "must be less than 4294967295 bytes");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (passwd_len <= 0) {
zend_error(E_WARNING, "empty password");
}
if (opslimit < crypto_pwhash_OPSLIMIT_MIN) {
- zend_argument_error(sodium_exception_ce, 2, "must be greater than or equal to %d", crypto_pwhash_OPSLIMIT_MIN);
+ zend_argument_value_error(2, "must be greater than or equal to %d", crypto_pwhash_OPSLIMIT_MIN);
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (memlimit < crypto_pwhash_MEMLIMIT_MIN) {
- zend_argument_error(sodium_exception_ce, 3, "must be greater than or equal to %d", crypto_pwhash_MEMLIMIT_MIN);
+ zend_argument_value_error(3, "must be greater than or equal to %d", crypto_pwhash_MEMLIMIT_MIN);
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
hash_str = zend_string_alloc(crypto_pwhash_STRBYTES - 1, 0);
@@ -1619,30 +1632,36 @@ PHP_FUNCTION(sodium_crypto_pwhash_scryptsalsa208sha256)
RETURN_THROWS();
}
if (hash_len <= 0 || hash_len >= ZSTR_MAX_LEN || hash_len > 0x1fffffffe0ULL) {
- zend_argument_error(sodium_exception_ce, 1, "must be greater than 0");
+ zend_argument_value_error(1, "must be greater than 0");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (opslimit <= 0) {
- zend_argument_error(sodium_exception_ce, 4, "must be greater than 0");
+ zend_argument_value_error(4, "must be greater than 0");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (memlimit <= 0 || memlimit > SIZE_MAX) {
- zend_argument_error(sodium_exception_ce, 5, "must be greater than 0");
+ zend_argument_value_error(5, "must be greater than 0");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (passwd_len <= 0) {
zend_error(E_WARNING, "empty password");
}
if (salt_len != crypto_pwhash_scryptsalsa208sha256_SALTBYTES) {
- zend_argument_error(sodium_exception_ce, 3, "must be SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES bytes long");
+ zend_argument_value_error(3, "must be SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES bytes long");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (opslimit < crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE) {
- zend_argument_error(sodium_exception_ce, 4, "must be greater than or equal to %d", crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE);
+ zend_argument_value_error(4, "must be greater than or equal to %d", crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE);
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (memlimit < crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE) {
- zend_argument_error(sodium_exception_ce, 5, "must be greater than or equal to %d", crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE);
+ zend_argument_value_error(5, "must be greater than or equal to %d", crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE);
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
hash = zend_string_alloc((size_t) hash_len, 0);
@@ -1674,22 +1693,26 @@ PHP_FUNCTION(sodium_crypto_pwhash_scryptsalsa208sha256_str)
RETURN_THROWS();
}
if (opslimit <= 0) {
- zend_argument_error(sodium_exception_ce, 2, "must be greater than 0");
+ zend_argument_value_error(2, "must be greater than 0");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (memlimit <= 0 || memlimit > SIZE_MAX) {
- zend_argument_error(sodium_exception_ce, 3, "must be greater than 0");
+ zend_argument_value_error(3, "must be greater than 0");
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (passwd_len <= 0) {
zend_error(E_WARNING, "empty password");
}
if (opslimit < crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE) {
- zend_argument_error(sodium_exception_ce, 2, "must be greater than or equal to %d", crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE);
+ zend_argument_value_error(2, "must be greater than or equal to %d", crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE);
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
if (memlimit < crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE) {
- zend_argument_error(sodium_exception_ce, 3, "must be greater than or equal to %d", crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE);
+ zend_argument_value_error(3, "must be greater than or equal to %d", crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE);
+ sodium_remove_param_values_from_backtrace(EG(exception));
RETURN_THROWS();
}
hash_str = zend_string_alloc
diff --git a/ext/sodium/tests/pwhash_memlimit_below_min.phpt b/ext/sodium/tests/pwhash_memlimit_below_min.phpt
index 63bf4443939..8913afb382f 100644
--- a/ext/sodium/tests/pwhash_memlimit_below_min.phpt
+++ b/ext/sodium/tests/pwhash_memlimit_below_min.phpt
@@ -12,13 +12,13 @@
try {
sodium_crypto_pwhash(32, "password", $salt, SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, 1);
-} catch (SodiumException $e) {
+} catch (\ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
sodium_crypto_pwhash_str("password", SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, 1);
-} catch (SodiumException $e) {
+} catch (\ValueError $e) {
echo $e->getMessage(), "\n";
}
?>
diff --git a/ext/sodium/tests/pwhash_valueerror_scrub.phpt b/ext/sodium/tests/pwhash_valueerror_scrub.phpt
new file mode 100644
index 00000000000..1cbd776e183
--- /dev/null
+++ b/ext/sodium/tests/pwhash_valueerror_scrub.phpt
@@ -0,0 +1,25 @@
+--TEST--
+sodium pwhash argument errors throw ValueError and keep the whole backtrace scrubbed
+--EXTENSIONS--
+sodium
+--SKIPIF--
+<?php
+if (!defined('SODIUM_CRYPTO_PWHASH_SALTBYTES')) print "skip libsodium without argon2";
+?>
+--FILE--
+<?php
+function wrap(string $password): void
+{
+ sodium_crypto_pwhash_str($password, SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, 1);
+}
+
+$secret = "hunter2-secret";
+wrap($secret);
+?>
+--EXPECTF--
+Fatal error: Uncaught ValueError: sodium_crypto_pwhash_str(): Argument #3 ($memlimit) must be greater than or equal to %d in %s:%d
+Stack trace:
+#0 %s(%d): sodium_crypto_pwhash_str()
+#1 %s(%d): wrap()
+#2 {main}
+ thrown in %s on line %d