From d03040d189a7b7c654091cb5e5355985d6b86e37 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 21 May 2024 16:37:25 +0200 Subject: [PATCH] Add php_random_bytes_ex() This variant can be used before RANDOM_G() is initialized --- Zend/zend_portability.h | 10 +++++ ext/random/csprng.c | 76 ++++++++++++++++++++++------------ ext/random/php_random_csprng.h | 6 ++- ext/random/random.c | 23 +++++----- 4 files changed, 75 insertions(+), 40 deletions(-) diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index af7dcf68ccd7b..99eb5ab135359 100644 --- a/Zend/zend_portability.h +++ b/Zend/zend_portability.h @@ -269,6 +269,16 @@ char *alloca(); # define ZEND_ATTRIBUTE_UNUSED #endif +#if ZEND_GCC_VERSION >= 3003 || __has_attribute(nonnull) +/* All pointer arguments must be non-null */ +# define ZEND_ATTRIBUTE_NONNULL __attribute__((nonnull)) +/* Specified arguments must be non-null (1-based) */ +# define ZEND_ATTRIBUTE_NONNULL_ARGS(...) __attribute__((nonnull(__VA_ARGS__))) +#else +# define ZEND_ATTRIBUTE_NONNULL +# define ZEND_ATTRIBUTE_NONNULL_ARGS(...) +#endif + #if defined(__GNUC__) && ZEND_GCC_VERSION >= 4003 # define ZEND_COLD __attribute__((cold)) # ifdef __OPTIMIZE__ diff --git a/ext/random/csprng.c b/ext/random/csprng.c index 1bb31bffa20ff..8b183afef5af7 100644 --- a/ext/random/csprng.c +++ b/ext/random/csprng.c @@ -26,6 +26,7 @@ #include "php.h" #include "Zend/zend_exceptions.h" +#include "Zend/zend_atomic.h" #include "php_random.h" #include "php_random_csprng.h" @@ -61,14 +62,16 @@ # include #endif -PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw) +#ifndef PHP_WIN32 +static zend_atomic_int random_fd = ZEND_ATOMIC_INT_INITIALIZER(-1); +#endif + +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_random_bytes_ex(void *bytes, size_t size, char *errstr, size_t errstr_size) { #ifdef PHP_WIN32 /* Defer to CryptGenRandom on Windows */ if (php_win32_get_random_bytes(bytes, size) == FAILURE) { - if (should_throw) { - zend_throw_exception(random_ce_Random_RandomException, "Failed to retrieve randomness from the operating system (BCryptGenRandom)", 0); - } + snprintf(errstr, errstr_size, "Failed to retrieve randomness from the operating system (BCryptGenRandom)"); return FAILURE; } #elif HAVE_COMMONCRYPTO_COMMONRANDOM_H @@ -79,9 +82,7 @@ PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw) * the vast majority of the time, it works fine ; but better make sure we catch failures */ if (CCRandomGenerateBytes(bytes, size) != kCCSuccess) { - if (should_throw) { - zend_throw_exception(random_ce_Random_RandomException, "Failed to retrieve randomness from the operating system (CCRandomGenerateBytes)", 0); - } + snprintf(errstr, errstr_size, "Failed to retrieve randomness from the operating system (CCRandomGenerateBytes)"); return FAILURE; } #elif HAVE_DECL_ARC4RANDOM_BUF && ((defined(__OpenBSD__) && OpenBSD >= 201405) || (defined(__NetBSD__) && __NetBSD_Version__ >= 700000001 && __NetBSD_Version__ < 1000000000) || \ @@ -147,19 +148,17 @@ PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw) } # endif if (read_bytes < size) { - int fd = RANDOM_G(random_fd); + int fd = zend_atomic_int_load_ex(&random_fd); struct stat st; if (fd < 0) { errno = 0; fd = open("/dev/urandom", O_RDONLY); if (fd < 0) { - if (should_throw) { - if (errno != 0) { - zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Cannot open /dev/urandom: %s", strerror(errno)); - } else { - zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Cannot open /dev/urandom"); - } + if (errno != 0) { + snprintf(errstr, errstr_size, "Cannot open /dev/urandom: %s", strerror(errno)); + } else { + snprintf(errstr, errstr_size, "Cannot open /dev/urandom"); } return FAILURE; } @@ -174,16 +173,19 @@ PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw) # endif ) { close(fd); - if (should_throw) { - if (errno != 0) { - zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Error reading from /dev/urandom: %s", strerror(errno)); - } else { - zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Error reading from /dev/urandom"); - } + if (errno != 0) { + snprintf(errstr, errstr_size, "Error reading from /dev/urandom: %s", strerror(errno)); + } else { + snprintf(errstr, errstr_size, "Error reading from /dev/urandom"); } return FAILURE; } - RANDOM_G(random_fd) = fd; + int expected = -1; + if (!zend_atomic_int_compare_exchange_ex(&random_fd, &expected, fd)) { + close(fd); + /* expected is now the actual value of random_fd */ + fd = expected; + } } read_bytes = 0; @@ -192,12 +194,10 @@ PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw) ssize_t n = read(fd, bytes + read_bytes, size - read_bytes); if (n <= 0) { - if (should_throw) { - if (errno != 0) { - zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Could not gather sufficient random data: %s", strerror(errno)); - } else { - zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Could not gather sufficient random data"); - } + if (errno != 0) { + snprintf(errstr, errstr_size, "Could not gather sufficient random data: %s", strerror(errno)); + } else { + snprintf(errstr, errstr_size, "Could not gather sufficient random data"); } return FAILURE; } @@ -210,6 +210,18 @@ PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw) return SUCCESS; } +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw) +{ + char errstr[128]; + zend_result result = php_random_bytes_ex(bytes, size, errstr, sizeof(errstr)); + + if (result == FAILURE && should_throw) { + zend_throw_exception(random_ce_Random_RandomException, errstr, 0); + } + + return result; +} + PHPAPI zend_result php_random_int(zend_long min, zend_long max, zend_long *result, bool should_throw) { zend_ulong umax; @@ -251,3 +263,13 @@ PHPAPI zend_result php_random_int(zend_long min, zend_long max, zend_long *resul *result = (zend_long)((trial % umax) + min); return SUCCESS; } + +PHPAPI void php_random_csprng_shutdown(void) +{ +#ifndef PHP_WIN32 + int fd = zend_atomic_int_exchange(&random_fd, -1); + if (fd != -1) { + close(fd); + } +#endif +} diff --git a/ext/random/php_random_csprng.h b/ext/random/php_random_csprng.h index 64a7050d50bd7..4b8c714cac308 100644 --- a/ext/random/php_random_csprng.h +++ b/ext/random/php_random_csprng.h @@ -20,7 +20,9 @@ # include "php.h" -PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw); +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw); +ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_random_bytes_ex(void *bytes, size_t size, char *errstr, size_t errstr_size); + PHPAPI zend_result php_random_int(zend_long min, zend_long max, zend_long *result, bool should_throw); static inline zend_result php_random_bytes_throw(void *bytes, size_t size) @@ -43,4 +45,6 @@ static inline zend_result php_random_int_silent(zend_long min, zend_long max, ze return php_random_int(min, max, result, false); } +PHPAPI void php_random_csprng_shutdown(void); + #endif /* PHP_RANDOM_CSPRNG_H */ diff --git a/ext/random/random.c b/ext/random/random.c index 8acf3ee17ef7b..99ca2409a3c5b 100644 --- a/ext/random/random.c +++ b/ext/random/random.c @@ -702,16 +702,6 @@ static PHP_GINIT_FUNCTION(random) } /* }}} */ -/* {{{ PHP_GSHUTDOWN_FUNCTION */ -static PHP_GSHUTDOWN_FUNCTION(random) -{ - if (random_globals->random_fd >= 0) { - close(random_globals->random_fd); - random_globals->random_fd = -1; - } -} -/* }}} */ - /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(random) { @@ -780,6 +770,15 @@ PHP_MINIT_FUNCTION(random) } /* }}} */ +/* {{{ PHP_MSHUTDOWN_FUNCTION */ +PHP_MSHUTDOWN_FUNCTION(random) +{ + php_random_csprng_shutdown(); + + return SUCCESS; +} +/* }}} */ + /* {{{ PHP_RINIT_FUNCTION */ PHP_RINIT_FUNCTION(random) { @@ -796,14 +795,14 @@ zend_module_entry random_module_entry = { "random", /* Extension name */ ext_functions, /* zend_function_entry */ PHP_MINIT(random), /* PHP_MINIT - Module initialization */ - NULL, /* PHP_MSHUTDOWN - Module shutdown */ + PHP_MSHUTDOWN(random), /* PHP_MSHUTDOWN - Module shutdown */ PHP_RINIT(random), /* PHP_RINIT - Request initialization */ NULL, /* PHP_RSHUTDOWN - Request shutdown */ NULL, /* PHP_MINFO - Module info */ PHP_VERSION, /* Version */ PHP_MODULE_GLOBALS(random), /* ZTS Module globals */ PHP_GINIT(random), /* PHP_GINIT - Global initialization */ - PHP_GSHUTDOWN(random), /* PHP_GSHUTDOWN - Global shutdown */ + NULL, /* PHP_GSHUTDOWN - Global shutdown */ NULL, /* Post deactivate */ STANDARD_MODULE_PROPERTIES_EX };