From d1048a0869ea0505db252daaeb3fec5eab027f1c Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 12 Jun 2024 13:57:54 +0200 Subject: [PATCH] Add zend_random_bytes(), zend_random_bytes_insecure() functions (#14054) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tim Düsterhus --- Zend/zend.c | 6 +++ Zend/zend.h | 15 +++++++ Zend/zend_portability.h | 20 ++++++++++ ext/random/config.m4 | 3 +- ext/random/config.w32 | 2 +- ext/random/php_random.h | 11 ++++- ext/random/php_random_zend_utils.h | 35 ++++++++++++++++ ext/random/random.c | 21 ++++++---- ext/random/zend_utils.c | 64 ++++++++++++++++++++++++++++++ main/main.c | 4 ++ 10 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 ext/random/php_random_zend_utils.h create mode 100644 ext/random/zend_utils.c diff --git a/Zend/zend.c b/Zend/zend.c index ada509181ce..1866ae76df6 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -94,6 +94,8 @@ ZEND_API char *(*zend_getenv)(const char *name, size_t name_len); ZEND_API zend_string *(*zend_resolve_path)(zend_string *filename); ZEND_API zend_result (*zend_post_startup_cb)(void) = NULL; ZEND_API void (*zend_post_shutdown_cb)(void) = NULL; +ZEND_ATTRIBUTE_NONNULL ZEND_API zend_result (*zend_random_bytes)(void *bytes, size_t size, char *errstr, size_t errstr_size) = NULL; +ZEND_ATTRIBUTE_NONNULL ZEND_API void (*zend_random_bytes_insecure)(zend_random_bytes_insecure_state *state, void *bytes, size_t size) = NULL; /* This callback must be signal handler safe! */ void (*zend_on_timeout)(int seconds); @@ -912,6 +914,10 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ php_win32_cp_set_by_id(65001); #endif + /* Set up early utility functions. */ + zend_random_bytes = utility_functions->random_bytes_function; + zend_random_bytes_insecure = utility_functions->random_bytes_insecure_function; + start_memory_manager(); virtual_cwd_startup(); /* Could use shutdown to free the main cwd but it would just slow it down for CGI */ diff --git a/Zend/zend.h b/Zend/zend.h index bc252f4706b..dd805e0e763 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -234,6 +234,11 @@ struct _zend_class_entry { } info; }; +typedef union { + zend_max_align_t align; + uint64_t opaque[5]; +} zend_random_bytes_insecure_state; + typedef struct _zend_utility_functions { void (*error_function)(int type, zend_string *error_filename, const uint32_t error_lineno, zend_string *message); size_t (*printf_function)(const char *format, ...) ZEND_ATTRIBUTE_PTR_FORMAT(printf, 1, 2); @@ -248,6 +253,8 @@ typedef struct _zend_utility_functions { void (*printf_to_smart_str_function)(smart_str *buf, const char *format, va_list ap); char *(*getenv_function)(const char *name, size_t name_len); zend_string *(*resolve_path_function)(zend_string *filename); + zend_result (*random_bytes_function)(void *bytes, size_t size, char *errstr, size_t errstr_size); + void (*random_bytes_insecure_function)(zend_random_bytes_insecure_state *state, void *bytes, size_t size); } zend_utility_functions; typedef struct _zend_utility_values { @@ -340,6 +347,14 @@ extern void (*zend_printf_to_smart_string)(smart_string *buf, const char *format extern void (*zend_printf_to_smart_str)(smart_str *buf, const char *format, va_list ap); extern ZEND_API char *(*zend_getenv)(const char *name, size_t name_len); extern ZEND_API zend_string *(*zend_resolve_path)(zend_string *filename); +/* Generate 'size' random bytes into 'bytes' with the OS CSPRNG. */ +extern ZEND_ATTRIBUTE_NONNULL ZEND_API zend_result (*zend_random_bytes)( + void *bytes, size_t size, char *errstr, size_t errstr_size); +/* Generate 'size' random bytes into 'bytes' with a general purpose PRNG (not + * crypto safe). 'state' must be zeroed before the first call and can be reused. + */ +extern ZEND_ATTRIBUTE_NONNULL ZEND_API void (*zend_random_bytes_insecure)( + zend_random_bytes_insecure_state *state, void *bytes, size_t size); /* These two callbacks are especially for opcache */ extern ZEND_API zend_result (*zend_post_startup_cb)(void); diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index 99eb5ab1353..c766491ba3b 100644 --- a/Zend/zend_portability.h +++ b/Zend/zend_portability.h @@ -791,4 +791,24 @@ extern "C++" { # define ZEND_STATIC_ASSERT(c, m) #endif +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) /* C11 */ \ + || (defined(__cplusplus) && __cplusplus >= 201103L) /* C++11 */ +typedef max_align_t zend_max_align_t; +#else +typedef union { + char c; + short s; + int i; + long l; +#if SIZEOF_LONG_LONG + long long ll; +#endif + float f; + double d; + long double ld; + void *p; + void (*fun)(); +} zend_max_align_t; +#endif + #endif /* ZEND_PORTABILITY_H */ diff --git a/ext/random/config.m4 b/ext/random/config.m4 index 2078b61ebcd..bb723c24401 100644 --- a/ext/random/config.m4 +++ b/ext/random/config.m4 @@ -27,6 +27,7 @@ PHP_NEW_EXTENSION(random, engine_secure.c \ engine_user.c \ gammasection.c \ - randomizer.c, + randomizer.c \ + zend_utils.c, no,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) PHP_INSTALL_HEADERS([ext/random], [php_random.h php_random_csprng.h php_random_uint128.h]) diff --git a/ext/random/config.w32 b/ext/random/config.w32 index 6b04fb6758b..307a4f0d292 100644 --- a/ext/random/config.w32 +++ b/ext/random/config.w32 @@ -1,4 +1,4 @@ EXTENSION("random", "random.c", false /* never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); PHP_RANDOM="yes"; -ADD_SOURCES(configure_module_dirname, "csprng.c engine_combinedlcg.c engine_mt19937.c engine_pcgoneseq128xslrr64.c engine_xoshiro256starstar.c engine_secure.c engine_user.c gammasection.c randomizer.c", "random"); +ADD_SOURCES(configure_module_dirname, "csprng.c engine_combinedlcg.c engine_mt19937.c engine_pcgoneseq128xslrr64.c engine_xoshiro256starstar.c engine_secure.c engine_user.c gammasection.c randomizer.c zend_utils.c", "random"); PHP_INSTALL_HEADERS("ext/random", "php_random.h php_random_csprng.h php_random_uint128.h"); diff --git a/ext/random/php_random.h b/ext/random/php_random.h index 9ec31993b6a..e4aa3212667 100644 --- a/ext/random/php_random.h +++ b/ext/random/php_random.h @@ -37,7 +37,10 @@ PHPAPI double php_combined_lcg(void); +typedef struct _php_random_fallback_seed_state php_random_fallback_seed_state; + PHPAPI uint64_t php_random_generate_fallback_seed(void); +PHPAPI uint64_t php_random_generate_fallback_seed_ex(php_random_fallback_seed_state *state); static inline zend_long GENERATE_SEED(void) { @@ -99,6 +102,11 @@ typedef struct _php_random_algo_with_state { void *state; } php_random_algo_with_state; +typedef struct _php_random_fallback_seed_state { + bool initialized; + unsigned char seed[20]; +} php_random_fallback_seed_state; + extern PHPAPI const php_random_algo php_random_algo_combinedlcg; extern PHPAPI const php_random_algo php_random_algo_mt19937; extern PHPAPI const php_random_algo php_random_algo_pcgoneseq128xslrr64; @@ -197,8 +205,7 @@ PHP_RINIT_FUNCTION(random); ZEND_BEGIN_MODULE_GLOBALS(random) bool combined_lcg_seeded; bool mt19937_seeded; - bool fallback_seed_initialized; - unsigned char fallback_seed[20]; + php_random_fallback_seed_state fallback_seed_state; php_random_status_state_combinedlcg combined_lcg; php_random_status_state_mt19937 mt19937; ZEND_END_MODULE_GLOBALS(random) diff --git a/ext/random/php_random_zend_utils.h b/ext/random/php_random_zend_utils.h new file mode 100644 index 00000000000..db37805904c --- /dev/null +++ b/ext/random/php_random_zend_utils.h @@ -0,0 +1,35 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Arnaud Le Blanc | + | Tim Düsterhus | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_RANDOM_ZEND_UTILS_H +# define PHP_RANDOM_ZEND_UTILS_H + +# include "php.h" +# include "php_random.h" +# include "zend.h" + +typedef struct _php_random_bytes_insecure_state_for_zend { + bool initialized; + php_random_status_state_xoshiro256starstar xoshiro256starstar_state; +} php_random_bytes_insecure_state_for_zend; + +ZEND_STATIC_ASSERT(sizeof(zend_random_bytes_insecure_state) >= sizeof(php_random_bytes_insecure_state_for_zend), ""); + +ZEND_ATTRIBUTE_NONNULL PHPAPI void php_random_bytes_insecure_for_zend( + zend_random_bytes_insecure_state *state, void *bytes, size_t size); + +#endif /* PHP_RANDOM_ZEND_UTILS_H */ diff --git a/ext/random/random.c b/ext/random/random.c index 252b2047d05..5db20eb6db4 100644 --- a/ext/random/random.c +++ b/ext/random/random.c @@ -610,7 +610,7 @@ static inline void fallback_seed_add(PHP_SHA1_CTX *c, void *p, size_t l){ PHP_SHA1Update(c, p, l); } -PHPAPI uint64_t php_random_generate_fallback_seed(void) +PHPAPI uint64_t php_random_generate_fallback_seed_ex(php_random_fallback_seed_state *state) { /* Mix various values using SHA-1 as a PRF to obtain as * much entropy as possible, hopefully generating an @@ -628,7 +628,7 @@ PHPAPI uint64_t php_random_generate_fallback_seed(void) char buf[64 + 1]; PHP_SHA1Init(&c); - if (!RANDOM_G(fallback_seed_initialized)) { + if (!state->initialized) { /* Current time. */ gettimeofday(&tv, NULL); fallback_seed_add(&c, &tv, sizeof(tv)); @@ -644,7 +644,7 @@ PHPAPI uint64_t php_random_generate_fallback_seed(void) fallback_seed_add(&c, &tid, sizeof(tid)); #endif /* Pointer values to benefit from ASLR. */ - pointer = &RANDOM_G(fallback_seed_initialized); + pointer = &state; fallback_seed_add(&c, &pointer, sizeof(pointer)); pointer = &c; fallback_seed_add(&c, &pointer, sizeof(pointer)); @@ -668,24 +668,29 @@ PHPAPI uint64_t php_random_generate_fallback_seed(void) gettimeofday(&tv, NULL); fallback_seed_add(&c, &tv, sizeof(tv)); /* Previous state. */ - fallback_seed_add(&c, RANDOM_G(fallback_seed), 20); + fallback_seed_add(&c, state->seed, 20); } - PHP_SHA1Final(RANDOM_G(fallback_seed), &c); - RANDOM_G(fallback_seed_initialized) = true; + PHP_SHA1Final(state->seed, &c); + state->initialized = true; uint64_t result = 0; for (size_t i = 0; i < sizeof(result); i++) { - result = result | (((uint64_t)RANDOM_G(fallback_seed)[i]) << (i * 8)); + result = result | (((uint64_t)state->seed[i]) << (i * 8)); } return result; } +PHPAPI uint64_t php_random_generate_fallback_seed(void) +{ + return php_random_generate_fallback_seed_ex(&RANDOM_G(fallback_seed_state)); +} + /* {{{ PHP_GINIT_FUNCTION */ static PHP_GINIT_FUNCTION(random) { - random_globals->fallback_seed_initialized = false; + random_globals->fallback_seed_state.initialized = false; } /* }}} */ diff --git a/ext/random/zend_utils.c b/ext/random/zend_utils.c new file mode 100644 index 00000000000..64ba2c87ef7 --- /dev/null +++ b/ext/random/zend_utils.c @@ -0,0 +1,64 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Arnaud Le Blanc | + | Tim Düsterhus | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "php_random_zend_utils.h" + +ZEND_ATTRIBUTE_NONNULL PHPAPI void php_random_bytes_insecure_for_zend( + zend_random_bytes_insecure_state *opaque_state, void *bytes, size_t size) +{ + php_random_bytes_insecure_state_for_zend *state = (php_random_bytes_insecure_state_for_zend*) opaque_state; + + if (UNEXPECTED(!state->initialized)) { + uint64_t t[4]; + php_random_fallback_seed_state fallback_state; + fallback_state.initialized = false; + + do { + /* Skip the CSPRNG if it has already failed */ + if (!fallback_state.initialized) { + char errstr[128]; + if (php_random_bytes_ex(&t, sizeof(t), errstr, sizeof(errstr)) == FAILURE) { +#if ZEND_DEBUG + fprintf(stderr, "php_random_bytes_ex: Failed to generate a random seed: %s\n", errstr); +#endif + goto fallback; + } + } else { +fallback: + t[0] = php_random_generate_fallback_seed_ex(&fallback_state); + t[1] = php_random_generate_fallback_seed_ex(&fallback_state); + t[2] = php_random_generate_fallback_seed_ex(&fallback_state); + t[3] = php_random_generate_fallback_seed_ex(&fallback_state); + } + } while (UNEXPECTED(t[0] == 0 && t[1] == 0 && t[2] == 0 && t[3] == 0)); + + php_random_xoshiro256starstar_seed256(&state->xoshiro256starstar_state, t[0], t[1], t[2], t[3]); + state->initialized = true; + } + + while (size > 0) { + php_random_result result = php_random_algo_xoshiro256starstar.generate(&state->xoshiro256starstar_state); + ZEND_ASSERT(result.size == 8 && sizeof(result.result) == 8); + size_t chunk_size = MIN(size, 8); + bytes = zend_mempcpy(bytes, &result.result, chunk_size); + size -= chunk_size; + } +} diff --git a/main/main.c b/main/main.c index a3acaf94b7f..a561a1db3fe 100644 --- a/main/main.c +++ b/main/main.c @@ -49,6 +49,8 @@ #include "fopen_wrappers.h" #include "ext/standard/php_standard.h" #include "ext/date/php_date.h" +#include "ext/random/php_random_csprng.h" +#include "ext/random/php_random_zend_utils.h" #include "php_variables.h" #include "ext/standard/credits.h" #ifdef PHP_WIN32 @@ -2119,6 +2121,8 @@ zend_result php_module_startup(sapi_module_struct *sf, zend_module_entry *additi zuf.printf_to_smart_str_function = php_printf_to_smart_str; zuf.getenv_function = sapi_getenv; zuf.resolve_path_function = php_resolve_path_for_zend; + zuf.random_bytes_function = php_random_bytes_ex; + zuf.random_bytes_insecure_function = php_random_bytes_insecure_for_zend; zend_startup(&zuf); zend_reset_lc_ctype_locale(); zend_update_current_locale();