Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,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
Expand Down
9 changes: 9 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,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.
Expand Down
69 changes: 46 additions & 23 deletions ext/sodium/libsodium.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not empty ValueError API.

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, "is too large");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
zend_argument_value_error(1, "is too large");
zend_argument_value_error(1, "must be less than %d bytes");

(or whatever the correct format specifier is)

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, "is too long");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

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
Expand All @@ -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);
Expand Down Expand Up @@ -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, "is too long");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions ext/sodium/tests/pwhash_memlimit_below_min.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ $salt = str_repeat("a", SODIUM_CRYPTO_PWHASH_SALTBYTES);

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";
}
?>
Expand Down
25 changes: 25 additions & 0 deletions ext/sodium/tests/pwhash_valueerror_scrub.phpt
Original file line number Diff line number Diff line change
@@ -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
Loading