diff --git a/Zend/zend.c b/Zend/zend.c index ada509181ceb0..efff18b7b675f 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,11 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ php_win32_cp_set_by_id(65001); #endif + /* Set up early utility functions. zend_mm depends on + * zend_random_bytes_insecure */ + 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 bc252f4706b0c..dd805e0e7638f 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_alloc.c b/Zend/zend_alloc.c index f92458328de9b..ed683d65fa221 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -54,10 +54,12 @@ #include "zend.h" #include "zend_alloc.h" #include "zend_globals.h" +#include "zend_hrtime.h" #include "zend_operators.h" #include "zend_multiply.h" #include "zend_bitset.h" #include "zend_mmap.h" +#include "zend_portability.h" #include #ifdef HAVE_UNISTD_H @@ -68,6 +70,8 @@ # include # include # include "win32/winutil.h" +# define getpid _getpid +typedef int pid_t; #endif #include @@ -141,6 +145,25 @@ static size_t _real_page_size = ZEND_MM_PAGE_SIZE; #ifndef ZEND_MM_ERROR # define ZEND_MM_ERROR 1 /* report system errors */ #endif +#ifndef ZEND_MM_HEAP_PROTECTION +# define ZEND_MM_HEAP_PROTECTION 1 /* protect heap against corruptions */ +#endif + +#if ZEND_MM_HEAP_PROTECTION +/* Define ZEND_MM_MIN_USEABLE_BIN_SIZE to the size of two pointers */ +# if UINTPTR_MAX == UINT64_MAX +# define ZEND_MM_MIN_USEABLE_BIN_SIZE 16 +# elif UINTPTR_MAX == UINT32_MAX +# define ZEND_MM_MIN_USEABLE_BIN_SIZE 8 +# else +# error +# endif +# if ZEND_MM_MIN_USEABLE_BIN_SIZE < ZEND_MM_MIN_SMALL_SIZE +# error +# endif +#else /* ZEND_MM_HEAP_PROTECTION */ +# define ZEND_MM_MIN_USEABLE_BIN_SIZE ZEND_MM_MIN_SMALL_SIZE +#endif /* ZEND_MM_HEAP_PROTECTION */ #ifndef ZEND_MM_CHECK # define ZEND_MM_CHECK(condition, message) do { \ @@ -194,6 +217,44 @@ typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */ #define ZEND_MM_BINS 30 +#if defined(_MSC_VER) +# if UINTPTR_MAX == UINT64_MAX +# define BSWAPPTR(u) _byteswap_uint64(u) +# else +# define BSWAPPTR(u) _byteswap_ulong(u) +# endif +#else +# if UINTPTR_MAX == UINT64_MAX +# if __has_builtin(__builtin_bswap64) +# define BSWAPPTR(u) __builtin_bswap64(u) +# else +static zend_always_inline uintptr_t BSWAPPTR(uintptr_t u) +{ + return (((u & 0xff00000000000000ULL) >> 56) + | ((u & 0x00ff000000000000ULL) >> 40) + | ((u & 0x0000ff0000000000ULL) >> 24) + | ((u & 0x000000ff00000000ULL) >> 8) + | ((u & 0x00000000ff000000ULL) << 8) + | ((u & 0x0000000000ff0000ULL) << 24) + | ((u & 0x000000000000ff00ULL) << 40) + | ((u & 0x00000000000000ffULL) << 56)); +} +# endif /* __has_builtin(__builtin_bswap64) */ +# else /* UINTPTR_MAX == UINT64_MAX */ +# if __has_builtin(__builtin_bswap32) +# define BSWAPPTR(u) __builtin_bswap32(u) +# else +static zend_always_inline uintptr_t BSWAPPTR(uintptr_t u) +{ + return (((u & 0xff000000) >> 24) + | ((u & 0x00ff0000) >> 8) + | ((u & 0x0000ff00) << 8) + | ((u & 0x000000ff) << 24)); +} +# endif /* __has_builtin(__builtin_bswap32) */ +# endif /* UINTPTR_MAX == UINT64_MAX */ +#endif /* defined(_MSC_VER) */ + typedef struct _zend_mm_page zend_mm_page; typedef struct _zend_mm_bin zend_mm_bin; typedef struct _zend_mm_free_slot zend_mm_free_slot; @@ -245,6 +306,7 @@ struct _zend_mm_heap { size_t size; /* current memory usage */ size_t peak; /* peak memory usage */ #endif + uintptr_t shadow_key; /* free slot shadow ptr xor key */ zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */ #if ZEND_MM_STAT || ZEND_MM_LIMIT size_t real_size; /* current size of allocated pages */ @@ -275,6 +337,8 @@ struct _zend_mm_heap { } custom_heap; HashTable *tracked_allocs; #endif + pid_t pid; + zend_random_bytes_insecure_state rand_state; }; struct _zend_mm_chunk { @@ -1248,6 +1312,67 @@ static zend_always_inline int zend_mm_small_size_to_bin(size_t size) #define ZEND_MM_SMALL_SIZE_TO_BIN(size) zend_mm_small_size_to_bin(size) +#if ZEND_MM_HEAP_PROTECTION +/* We keep track of free slots by organizing them in a linked list, with the + * first word of every free slot being a pointer to the next one. + * + * In order to frustrate corruptions, we check the consistency of these pointers + * before dereference by comparing them with a shadow. + * + * The shadow is a copy of the pointer, stored at the end of the slot. It is + * XOR'ed with a random key, and converted to big-endian so that smaller + * corruptions affect the most significant bytes, which has a high chance of + * resulting in an invalid address instead of pointing to an adjacent slot. + */ + +#define ZEND_MM_FREE_SLOT_PTR_SHADOW(free_slot, bin_num) \ + *((zend_mm_free_slot**)((char*)(free_slot) + bin_data_size[(bin_num)] - sizeof(zend_mm_free_slot*))) + +static zend_always_inline zend_mm_free_slot* zend_mm_encode_free_slot(const zend_mm_heap *heap, const zend_mm_free_slot *slot) +{ +#ifdef WORDS_BIGENDIAN + return (zend_mm_free_slot*)(((uintptr_t)slot) ^ heap->shadow_key); +#else + return (zend_mm_free_slot*)(BSWAPPTR((uintptr_t)slot) ^ heap->shadow_key); +#endif +} + +static zend_always_inline zend_mm_free_slot* zend_mm_decode_free_slot(zend_mm_heap *heap, zend_mm_free_slot *slot) +{ +#ifdef WORDS_BIGENDIAN + return (zend_mm_free_slot*)((uintptr_t)slot ^ heap->shadow_key); +#else + return (zend_mm_free_slot*)(BSWAPPTR((uintptr_t)slot ^ heap->shadow_key)); +#endif +} + +static zend_always_inline void zend_mm_set_next_free_slot(zend_mm_heap *heap, uint32_t bin_num, zend_mm_free_slot *slot, zend_mm_free_slot *next) +{ + ZEND_ASSERT(bin_data_size[bin_num] >= ZEND_MM_MIN_USEABLE_BIN_SIZE); + + slot->next_free_slot = next; + ZEND_MM_FREE_SLOT_PTR_SHADOW(slot, bin_num) = zend_mm_encode_free_slot(heap, next); +} + +static zend_always_inline zend_mm_free_slot *zend_mm_get_next_free_slot(zend_mm_heap *heap, uint32_t bin_num, zend_mm_free_slot* slot) +{ + zend_mm_free_slot *next = slot->next_free_slot; + if (EXPECTED(next != NULL)) { + zend_mm_free_slot *shadow = ZEND_MM_FREE_SLOT_PTR_SHADOW(slot, bin_num); + if (UNEXPECTED(next != zend_mm_decode_free_slot(heap, shadow))) { + zend_mm_panic("zend_mm_heap corrupted"); + } + } + return (zend_mm_free_slot*)next; +} + +#else /* ZEND_MM_HEAP_PROTECTION */ +# define zend_mm_set_next_free_slot(heap, bin_num, slot, next) do { \ + (slot)->next_free_slot = (next); \ + } while (0) +# define zend_mm_get_next_free_slot(heap, bin_num, slot) (slot)->next_free_slot +#endif /* ZEND_MM_HEAP_PROTECTION */ + static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, uint32_t bin_num ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { zend_mm_chunk *chunk; @@ -1281,7 +1406,7 @@ static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, uint end = (zend_mm_free_slot*)((char*)bin + (bin_data_size[bin_num] * (bin_elements[bin_num] - 1))); heap->free_slot[bin_num] = p = (zend_mm_free_slot*)((char*)bin + bin_data_size[bin_num]); do { - p->next_free_slot = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]); + zend_mm_set_next_free_slot(heap, bin_num, p, (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num])); #if ZEND_DEBUG do { zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + bin_data_size[bin_num] - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); @@ -1306,6 +1431,8 @@ static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, uint static zend_always_inline void *zend_mm_alloc_small(zend_mm_heap *heap, int bin_num ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { + ZEND_ASSERT(bin_data_size[bin_num] >= ZEND_MM_MIN_USEABLE_BIN_SIZE); + #if ZEND_MM_STAT do { size_t size = heap->size + bin_data_size[bin_num]; @@ -1317,7 +1444,7 @@ static zend_always_inline void *zend_mm_alloc_small(zend_mm_heap *heap, int bin_ if (EXPECTED(heap->free_slot[bin_num] != NULL)) { zend_mm_free_slot *p = heap->free_slot[bin_num]; - heap->free_slot[bin_num] = p->next_free_slot; + heap->free_slot[bin_num] = zend_mm_get_next_free_slot(heap, bin_num, p); return p; } else { return zend_mm_alloc_small_slow(heap, bin_num ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); @@ -1326,6 +1453,8 @@ static zend_always_inline void *zend_mm_alloc_small(zend_mm_heap *heap, int bin_ static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr, int bin_num) { + ZEND_ASSERT(bin_data_size[bin_num] >= ZEND_MM_MIN_USEABLE_BIN_SIZE); + zend_mm_free_slot *p; #if ZEND_MM_STAT @@ -1340,7 +1469,7 @@ static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr, #endif p = (zend_mm_free_slot*)ptr; - p->next_free_slot = heap->free_slot[bin_num]; + zend_mm_set_next_free_slot(heap, bin_num, p, heap->free_slot[bin_num]); heap->free_slot[bin_num] = p; } @@ -1375,6 +1504,11 @@ static zend_always_inline zend_mm_debug_info *zend_mm_get_debug_info(zend_mm_hea static zend_always_inline void *zend_mm_alloc_heap(zend_mm_heap *heap, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { void *ptr; +#if ZEND_MM_HEAP_PROTECTION + if (size < ZEND_MM_MIN_USEABLE_BIN_SIZE) { + size = ZEND_MM_MIN_USEABLE_BIN_SIZE; + } +#endif /* ZEND_MM_HEAP_PROTECTION */ #if ZEND_DEBUG size_t real_size = size; zend_mm_debug_info *dbg; @@ -1596,6 +1730,11 @@ static zend_always_inline void *zend_mm_realloc_heap(zend_mm_heap *heap, void *p zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); int page_num = (int)(page_offset / ZEND_MM_PAGE_SIZE); zend_mm_page_info info = chunk->map[page_num]; +#if ZEND_MM_HEAP_PROTECTION + if (size < ZEND_MM_MIN_USEABLE_BIN_SIZE) { + size = ZEND_MM_MIN_USEABLE_BIN_SIZE; + } +#endif /* ZEND_MM_HEAP_PROTECTION */ #if ZEND_DEBUG size_t real_size = size; @@ -1904,6 +2043,17 @@ static void zend_mm_free_huge(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZE /* Initialization */ /******************/ +static void zend_mm_refresh_key(zend_mm_heap *heap) +{ + zend_random_bytes_insecure(&heap->rand_state, &heap->shadow_key, sizeof(heap->shadow_key)); +} + +static void zend_mm_init_key(zend_mm_heap *heap) +{ + memset(&heap->rand_state, 0, sizeof(heap->rand_state)); + zend_mm_refresh_key(heap); +} + static zend_mm_heap *zend_mm_init(void) { zend_mm_chunk *chunk = (zend_mm_chunk*)zend_mm_chunk_alloc_int(ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE); @@ -1940,6 +2090,7 @@ static zend_mm_heap *zend_mm_init(void) heap->size = 0; heap->peak = 0; #endif + zend_mm_init_key(heap); #if ZEND_MM_LIMIT heap->limit = (size_t)Z_L(-1) >> 1; heap->overflow = 0; @@ -1951,12 +2102,13 @@ static zend_mm_heap *zend_mm_init(void) heap->storage = NULL; #endif heap->huge_list = NULL; + heap->pid = getpid(); return heap; } ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) { - zend_mm_free_slot *p, **q; + zend_mm_free_slot *p, *q; zend_mm_chunk *chunk; size_t page_offset; int page_num; @@ -1994,15 +2146,15 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) has_free_pages = true; } chunk->map[page_num] = ZEND_MM_SRUN_EX(i, free_counter); - p = p->next_free_slot; + p = zend_mm_get_next_free_slot(heap, i, p); } if (!has_free_pages) { continue; } - q = &heap->free_slot[i]; - p = *q; + q = (zend_mm_free_slot*)&heap->free_slot[i]; + p = q->next_free_slot; while (p != NULL) { chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(p, ZEND_MM_CHUNK_SIZE); ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); @@ -2020,11 +2172,19 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(info) == i); if (ZEND_MM_SRUN_FREE_COUNTER(info) == bin_elements[i]) { /* remove from cache */ - p = p->next_free_slot; - *q = p; + p = zend_mm_get_next_free_slot(heap, i, p); + if (q == (zend_mm_free_slot*)&heap->free_slot[i]) { + q->next_free_slot = p; + } else { + zend_mm_set_next_free_slot(heap, i, q, p); + } } else { - q = &p->next_free_slot; - p = *q; + q = p; + if (q == (zend_mm_free_slot*)&heap->free_slot[i]) { + p = q->next_free_slot; + } else { + p = zend_mm_get_next_free_slot(heap, i, q); + } } } } @@ -2376,6 +2536,14 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent) memset(p->free_map, 0, sizeof(p->free_map) + sizeof(p->map)); p->free_map[0] = (1L << ZEND_MM_FIRST_PAGE) - 1; p->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE); + + pid_t pid = getpid(); + if (heap->pid != pid) { + zend_mm_init_key(heap); + heap->pid = pid; + } else { + zend_mm_refresh_key(heap); + } } } @@ -2507,13 +2675,16 @@ ZEND_API bool is_zend_ptr(const void *ptr) # define ZEND_MM_CUSTOM_DEALLOCATOR(ptr) #endif -# define _ZEND_BIN_ALLOCATOR(_num, _size, _elements, _pages, x, y) \ +# define _ZEND_BIN_ALLOCATOR(_num, _size, _elements, _pages, _min_size, y) \ ZEND_API void* ZEND_FASTCALL _emalloc_ ## _size(void) { \ ZEND_MM_CUSTOM_ALLOCATOR(_size); \ + if (_size < _min_size) { \ + return _emalloc_ ## _min_size(); \ + } \ return zend_mm_alloc_small(AG(mm_heap), _num ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \ } -ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR, x, y) +ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR, ZEND_MM_MIN_USEABLE_BIN_SIZE, y) ZEND_API void* ZEND_FASTCALL _emalloc_large(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { @@ -2528,9 +2699,13 @@ ZEND_API void* ZEND_FASTCALL _emalloc_huge(size_t size) } #if ZEND_DEBUG -# define _ZEND_BIN_FREE(_num, _size, _elements, _pages, x, y) \ +# define _ZEND_BIN_FREE(_num, _size, _elements, _pages, _min_size, y) \ ZEND_API void ZEND_FASTCALL _efree_ ## _size(void *ptr) { \ ZEND_MM_CUSTOM_DEALLOCATOR(ptr); \ + if (_size < _min_size) { \ + _efree_ ## _min_size(ptr); \ + return; \ + } \ { \ size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); \ zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); \ @@ -2542,9 +2717,13 @@ ZEND_API void* ZEND_FASTCALL _emalloc_huge(size_t size) } \ } #else -# define _ZEND_BIN_FREE(_num, _size, _elements, _pages, x, y) \ +# define _ZEND_BIN_FREE(_num, _size, _elements, _pages, _min_size, y) \ ZEND_API void ZEND_FASTCALL _efree_ ## _size(void *ptr) { \ ZEND_MM_CUSTOM_DEALLOCATOR(ptr); \ + if (_size < _min_size) { \ + _efree_ ## _min_size(ptr); \ + return; \ + } \ { \ zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); \ ZEND_MM_CHECK(chunk->heap == AG(mm_heap), "zend_mm_heap corrupted"); \ @@ -2553,7 +2732,7 @@ ZEND_API void* ZEND_FASTCALL _emalloc_huge(size_t size) } #endif -ZEND_MM_BINS_INFO(_ZEND_BIN_FREE, x, y) +ZEND_MM_BINS_INFO(_ZEND_BIN_FREE, ZEND_MM_MIN_USEABLE_BIN_SIZE, y) ZEND_API void ZEND_FASTCALL _efree_large(void *ptr, size_t size) { @@ -3052,6 +3231,7 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void heap->size = 0; heap->peak = 0; #endif + zend_mm_init_key(heap); #if ZEND_MM_LIMIT heap->limit = (size_t)Z_L(-1) >> 1; heap->overflow = 0; @@ -3076,6 +3256,7 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void memcpy(storage->data, data, data_size); } heap->storage = storage; + heap->pid = getpid(); return heap; #else return NULL; diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index 99eb5ab135359..c766491ba3b9f 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 2078b61ebcdca..bb723c2440158 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 6b04fb6758b1c..307a4f0d292be 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 9ec31993b6adb..e4aa32126671e 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 0000000000000..db37805904cbd --- /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 7a31f0a01d954..ba5d069630b59 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 0000000000000..64ba2c87ef70a --- /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 a3acaf94b7f8f..a561a1db3fef7 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();