diff --git a/NEWS b/NEWS index 613470d2a0e4a..934dc2791097b 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ PHP NEWS PcgOneseq128XslRr64::__construct()). (timwolla) . Removed redundant RuntimeExceptions from Randomizer methods. The exceptions thrown by the engines will be exposed directly. (timwolla) + . Added extension specific Exceptions/Errors (RandomException, RandomError, + BrokenRandomEngineError). (timwolla) 04 Aug 2022, PHP 8.2.0beta2 diff --git a/UPGRADING b/UPGRADING index 75f65abdd3665..09823f01fecfb 100644 --- a/UPGRADING +++ b/UPGRADING @@ -201,6 +201,10 @@ PHP 8.2 UPGRADE NOTES dba_fetch(string|array $key, $skip, $dba): string|false is still accepted, but it is recommended to use the new standard variant. +- Random + . random_bytes() and random_int() now throw \Random\RandomException on CSPRNG failure. + Previously a plain \Exception was thrown. + - SPL . The $iterator parameter of iterator_to_array() and iterator_count() is widened to iterable from Iterator, allowing arrays to be passed. diff --git a/ext/random/engine_combinedlcg.c b/ext/random/engine_combinedlcg.c index 527ff168e8ccf..2fa26860ec2ce 100644 --- a/ext/random/engine_combinedlcg.c +++ b/ext/random/engine_combinedlcg.c @@ -22,7 +22,6 @@ #include "php.h" #include "php_random.h" -#include "ext/spl/spl_exceptions.h" #include "Zend/zend_exceptions.h" /* diff --git a/ext/random/engine_mt19937.c b/ext/random/engine_mt19937.c index bfddc96545803..d7d355b90d6b4 100644 --- a/ext/random/engine_mt19937.c +++ b/ext/random/engine_mt19937.c @@ -30,7 +30,6 @@ #include "php.h" #include "php_random.h" -#include "ext/spl/spl_exceptions.h" #include "Zend/zend_exceptions.h" /* @@ -280,7 +279,7 @@ PHP_METHOD(Random_Engine_Mt19937, __construct) if (seed_is_null) { /* MT19937 has a very large state, uses CSPRNG for seeding only */ if (php_random_bytes_throw(&seed, sizeof(zend_long)) == FAILURE) { - zend_throw_exception(spl_ce_RuntimeException, "Failed to generate a random seed", 0); + zend_throw_exception(random_ce_Random_RandomException, "Failed to generate a random seed", 0); RETURN_THROWS(); } } diff --git a/ext/random/engine_pcgoneseq128xslrr64.c b/ext/random/engine_pcgoneseq128xslrr64.c index 40e9e3d653252..41bbdad98a422 100644 --- a/ext/random/engine_pcgoneseq128xslrr64.c +++ b/ext/random/engine_pcgoneseq128xslrr64.c @@ -23,7 +23,6 @@ #include "php.h" #include "php_random.h" -#include "ext/spl/spl_exceptions.h" #include "Zend/zend_exceptions.h" static inline void step(php_random_status_state_pcgoneseq128xslrr64 *s) @@ -149,7 +148,7 @@ PHP_METHOD(Random_Engine_PcgOneseq128XslRr64, __construct) if (seed_is_null) { if (php_random_bytes_throw(&state->state, sizeof(php_random_uint128_t)) == FAILURE) { - zend_throw_exception(spl_ce_RuntimeException, "Failed to generate a random seed", 0); + zend_throw_exception(random_ce_Random_RandomException, "Failed to generate a random seed", 0); RETURN_THROWS(); } } else { diff --git a/ext/random/engine_secure.c b/ext/random/engine_secure.c index d6c9faa067679..8cc5d9cb6f249 100644 --- a/ext/random/engine_secure.c +++ b/ext/random/engine_secure.c @@ -22,7 +22,6 @@ #include "php.h" #include "php_random.h" -#include "ext/spl/spl_exceptions.h" #include "Zend/zend_exceptions.h" static uint64_t generate(php_random_status *status) diff --git a/ext/random/engine_user.c b/ext/random/engine_user.c index 149e572dcb772..b45924d3bb7da 100644 --- a/ext/random/engine_user.c +++ b/ext/random/engine_user.c @@ -49,7 +49,7 @@ static uint64_t generate(php_random_status *status) result += ((uint64_t) (unsigned char) Z_STRVAL(retval)[i]) << (8 * i); } } else { - zend_throw_error(NULL, "A random engine must return a non-empty string"); + zend_throw_error(random_ce_Random_BrokenRandomEngineError, "A random engine must return a non-empty string"); return 0; } diff --git a/ext/random/engine_xoshiro256starstar.c b/ext/random/engine_xoshiro256starstar.c index f964146b2e68a..1e48f3470da13 100644 --- a/ext/random/engine_xoshiro256starstar.c +++ b/ext/random/engine_xoshiro256starstar.c @@ -24,7 +24,6 @@ #include "php.h" #include "php_random.h" -#include "ext/spl/spl_exceptions.h" #include "Zend/zend_exceptions.h" static inline uint64_t splitmix64(uint64_t *seed) @@ -207,7 +206,7 @@ PHP_METHOD(Random_Engine_Xoshiro256StarStar, __construct) if (seed_is_null) { if (php_random_bytes_throw(&state->state, 32) == FAILURE) { - zend_throw_exception(spl_ce_RuntimeException, "Failed to generate a random seed", 0); + zend_throw_exception(random_ce_Random_RandomException, "Failed to generate a random seed", 0); RETURN_THROWS(); } } else { diff --git a/ext/random/php_random.h b/ext/random/php_random.h index 6c369c5086de5..cc05a03c86932 100644 --- a/ext/random/php_random.h +++ b/ext/random/php_random.h @@ -260,6 +260,10 @@ typedef struct _php_random_randomizer { extern PHPAPI zend_class_entry *random_ce_Random_Engine; extern PHPAPI zend_class_entry *random_ce_Random_CryptoSafeEngine; +extern PHPAPI zend_class_entry *random_ce_Random_RandomError; +extern PHPAPI zend_class_entry *random_ce_Random_BrokenRandomEngineError; +extern PHPAPI zend_class_entry *random_ce_Random_RandomException; + extern PHPAPI zend_class_entry *random_ce_Random_Engine_PcgOneseq128XslRr64; extern PHPAPI zend_class_entry *random_ce_Random_Engine_Mt19937; extern PHPAPI zend_class_entry *random_ce_Random_Engine_Xoshiro256StarStar; diff --git a/ext/random/random.c b/ext/random/random.c index ec9877544844e..ba25e5c04f041 100644 --- a/ext/random/random.c +++ b/ext/random/random.c @@ -26,7 +26,6 @@ #include "php.h" -#include "ext/spl/spl_exceptions.h" #include "Zend/zend_exceptions.h" #include "php_random.h" @@ -74,8 +73,13 @@ PHPAPI zend_class_entry *random_ce_Random_Engine_Mt19937; PHPAPI zend_class_entry *random_ce_Random_Engine_PcgOneseq128XslRr64; PHPAPI zend_class_entry *random_ce_Random_Engine_Xoshiro256StarStar; PHPAPI zend_class_entry *random_ce_Random_Engine_Secure; + PHPAPI zend_class_entry *random_ce_Random_Randomizer; +PHPAPI zend_class_entry *random_ce_Random_RandomError; +PHPAPI zend_class_entry *random_ce_Random_BrokenRandomEngineError; +PHPAPI zend_class_entry *random_ce_Random_RandomException; + static zend_object_handlers random_engine_mt19937_object_handlers; static zend_object_handlers random_engine_pcgoneseq128xslrr64_object_handlers; static zend_object_handlers random_engine_xoshiro256starstar_object_handlers; @@ -121,7 +125,7 @@ static inline uint32_t rand_range32(const php_random_algo *algo, php_random_stat while (UNEXPECTED(result > limit)) { /* If the requirements cannot be met in a cycles, return fail */ if (++count > RANDOM_RANGE_ATTEMPTS) { - zend_throw_error(NULL, "Failed to generate an acceptable random number in %d attempts", RANDOM_RANGE_ATTEMPTS); + zend_throw_error(random_ce_Random_BrokenRandomEngineError, "Failed to generate an acceptable random number in %d attempts", RANDOM_RANGE_ATTEMPTS); return 0; } @@ -177,7 +181,7 @@ static inline uint64_t rand_range64(const php_random_algo *algo, php_random_stat while (UNEXPECTED(result > limit)) { /* If the requirements cannot be met in a cycles, return fail */ if (++count > RANDOM_RANGE_ATTEMPTS) { - zend_throw_error(NULL, "Failed to generate an acceptable random number in %d attempts", RANDOM_RANGE_ATTEMPTS); + zend_throw_error(random_ce_Random_BrokenRandomEngineError, "Failed to generate an acceptable random number in %d attempts", RANDOM_RANGE_ATTEMPTS); return 0; } @@ -470,7 +474,7 @@ PHPAPI int php_random_bytes(void *bytes, size_t size, bool should_throw) /* Defer to CryptGenRandom on Windows */ if (php_win32_get_random_bytes(bytes, size) == FAILURE) { if (should_throw) { - zend_throw_exception(zend_ce_exception, "Failed to retrieve randomness from the operating system (BCryptGenRandom)", 0); + zend_throw_exception(random_ce_Random_RandomException, "Failed to retrieve randomness from the operating system (BCryptGenRandom)", 0); } return FAILURE; } @@ -483,7 +487,7 @@ PHPAPI int php_random_bytes(void *bytes, size_t size, bool should_throw) */ if (CCRandomGenerateBytes(bytes, size) != kCCSuccess) { if (should_throw) { - zend_throw_exception(zend_ce_exception, "Failed to retrieve randomness from the operating system (CCRandomGenerateBytes)", 0); + zend_throw_exception(random_ce_Random_RandomException, "Failed to retrieve randomness from the operating system (CCRandomGenerateBytes)", 0); } return FAILURE; } @@ -548,9 +552,9 @@ PHPAPI int php_random_bytes(void *bytes, size_t size, bool should_throw) if (fd < 0) { if (should_throw) { if (errno != 0) { - zend_throw_exception_ex(zend_ce_exception, 0, "Cannot open /dev/urandom: %s", strerror(errno)); + zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Cannot open /dev/urandom: %s", strerror(errno)); } else { - zend_throw_exception_ex(zend_ce_exception, 0, "Cannot open /dev/urandom"); + zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Cannot open /dev/urandom"); } } return FAILURE; @@ -568,9 +572,9 @@ PHPAPI int php_random_bytes(void *bytes, size_t size, bool should_throw) close(fd); if (should_throw) { if (errno != 0) { - zend_throw_exception_ex(zend_ce_exception, 0, "Error reading from /dev/urandom: %s", strerror(errno)); + zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Error reading from /dev/urandom: %s", strerror(errno)); } else { - zend_throw_exception_ex(zend_ce_exception, 0, "Error reading from /dev/urandom"); + zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Error reading from /dev/urandom"); } } return FAILURE; @@ -589,9 +593,9 @@ PHPAPI int php_random_bytes(void *bytes, size_t size, bool should_throw) if (read_bytes < size) { if (should_throw) { if (errno != 0) { - zend_throw_exception_ex(zend_ce_exception, 0, "Could not gather sufficient random data: %s", strerror(errno)); + zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Could not gather sufficient random data: %s", strerror(errno)); } else { - zend_throw_exception_ex(zend_ce_exception, 0, "Could not gather sufficient random data"); + zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Could not gather sufficient random data"); } } return FAILURE; @@ -832,6 +836,15 @@ PHP_MINIT_FUNCTION(random) /* Random\CryptoSafeEngine */ random_ce_Random_CryptoSafeEngine = register_class_Random_CryptoSafeEngine(random_ce_Random_Engine); + /* Random\RandomError */ + random_ce_Random_RandomError = register_class_Random_RandomError(zend_ce_error); + + /* Random\BrokenRandomEngineError */ + random_ce_Random_BrokenRandomEngineError = register_class_Random_BrokenRandomEngineError(random_ce_Random_RandomError); + + /* Random\RandomException */ + random_ce_Random_RandomException = register_class_Random_RandomException(zend_ce_exception); + /* Random\Engine\Mt19937 */ random_ce_Random_Engine_Mt19937 = register_class_Random_Engine_Mt19937(random_ce_Random_Engine); random_ce_Random_Engine_Mt19937->create_object = php_random_engine_mt19937_new; diff --git a/ext/random/random.stub.php b/ext/random/random.stub.php index 754087f3ce4e0..0a178f2657dc2 100644 --- a/ext/random/random.stub.php +++ b/ext/random/random.stub.php @@ -147,4 +147,25 @@ public function __serialize(): array {} public function __unserialize(array $data): void {} } + + /** + * @strict-properties + */ + class RandomError extends \Error + { + } + + /** + * @strict-properties + */ + class BrokenRandomEngineError extends RandomError + { + } + + /** + * @strict-properties + */ + class RandomException extends \Exception + { + } } diff --git a/ext/random/random_arginfo.h b/ext/random/random_arginfo.h index 3aaf4fcc1fad4..440e77c62ef45 100644 --- a/ext/random/random_arginfo.h +++ b/ext/random/random_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 82403b033dfd476695c4e11e0b01a3c984896f62 */ + * Stub hash: 6cc9022516ce23c2e95af30606db43e9fc28e38a */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_lcg_value, 0, 0, IS_DOUBLE, 0) ZEND_END_ARG_INFO() @@ -217,6 +217,21 @@ static const zend_function_entry class_Random_Randomizer_methods[] = { ZEND_FE_END }; + +static const zend_function_entry class_Random_RandomError_methods[] = { + ZEND_FE_END +}; + + +static const zend_function_entry class_Random_BrokenRandomEngineError_methods[] = { + ZEND_FE_END +}; + + +static const zend_function_entry class_Random_RandomException_methods[] = { + ZEND_FE_END +}; + static void register_random_symbols(int module_number) { REGISTER_LONG_CONSTANT("MT_RAND_MT19937", MT_RAND_MT19937, CONST_CS | CONST_PERSISTENT); @@ -309,3 +324,36 @@ static zend_class_entry *register_class_Random_Randomizer(void) return class_entry; } + +static zend_class_entry *register_class_Random_RandomError(zend_class_entry *class_entry_Error) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Random", "RandomError", class_Random_RandomError_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_Error); + class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES; + + return class_entry; +} + +static zend_class_entry *register_class_Random_BrokenRandomEngineError(zend_class_entry *class_entry_Random_RandomError) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Random", "BrokenRandomEngineError", class_Random_BrokenRandomEngineError_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_Random_RandomError); + class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES; + + return class_entry; +} + +static zend_class_entry *register_class_Random_RandomException(zend_class_entry *class_entry_Exception) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Random", "RandomException", class_Random_RandomException_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_Exception); + class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES; + + return class_entry; +} diff --git a/ext/random/tests/03_randomizer/user_unsafe.phpt b/ext/random/tests/03_randomizer/user_unsafe.phpt index e95a15840057d..6e6a8151fdf11 100644 --- a/ext/random/tests/03_randomizer/user_unsafe.phpt +++ b/ext/random/tests/03_randomizer/user_unsafe.phpt @@ -74,35 +74,35 @@ foreach ([ EmptyStringEngine ===================== -Error: A random engine must return a non-empty string in %s:%d +Random\BrokenRandomEngineError: A random engine must return a non-empty string in %s:%d Stack trace: #0 %s(%d): Random\Randomizer->getInt(0, 123) #1 {main} ------- -Error: A random engine must return a non-empty string in %s:%d +Random\BrokenRandomEngineError: A random engine must return a non-empty string in %s:%d Stack trace: #0 %s(%d): Random\Randomizer->nextInt() #1 {main} ------- -Error: A random engine must return a non-empty string in %s:%d +Random\BrokenRandomEngineError: A random engine must return a non-empty string in %s:%d Stack trace: #0 %s(%d): Random\Randomizer->getBytes(1) #1 {main} ------- -Error: A random engine must return a non-empty string in %s:%d +Random\BrokenRandomEngineError: A random engine must return a non-empty string in %s:%d Stack trace: #0 %s(%d): Random\Randomizer->shuffleArray(Array) #1 {main} ------- -Error: A random engine must return a non-empty string in %s:%d +Random\BrokenRandomEngineError: A random engine must return a non-empty string in %s:%d Stack trace: #0 %s(%d): Random\Randomizer->shuffleBytes('foobar') #1 {main} @@ -111,7 +111,7 @@ Stack trace: HeavilyBiasedEngine ===================== -Error: Failed to generate an acceptable random number in 50 attempts in %s:%d +Random\BrokenRandomEngineError: Failed to generate an acceptable random number in 50 attempts in %s:%d Stack trace: #0 %s(%d): Random\Randomizer->getInt(0, 123) #1 {main} @@ -126,14 +126,14 @@ string(2) "ff" ------- -Error: Failed to generate an acceptable random number in 50 attempts in %s:%d +Random\BrokenRandomEngineError: Failed to generate an acceptable random number in 50 attempts in %s:%d Stack trace: #0 %s(%d): Random\Randomizer->shuffleArray(Array) #1 {main} ------- -Error: Failed to generate an acceptable random number in 50 attempts in %s:%d +Random\BrokenRandomEngineError: Failed to generate an acceptable random number in 50 attempts in %s:%d Stack trace: #0 %s(%d): Random\Randomizer->shuffleBytes('foobar') #1 {main}