From e0f296cedcc7040023822add4b49afe80e2af39c Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Sat, 8 Aug 2026 08:10:03 +0200 Subject: [PATCH 01/23] Added PHP_SYS_SIZE from SIZEOF_SIZE_T --- main/main.stub.php | 5 +++++ main/main_arginfo.h | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/main/main.stub.php b/main/main.stub.php index af19a7f73731..63d0dc1e69ed 100644 --- a/main/main.stub.php +++ b/main/main.stub.php @@ -171,6 +171,11 @@ * @cvalue SIZEOF_ZEND_LONG */ const PHP_INT_SIZE = UNKNOWN; +/** + * @var int + * @cvalue SIZEOF_SIZE_T + */ +const PHP_SYS_SIZE = UNKNOWN; /** * @var int * @cvalue FD_SETSIZE diff --git a/main/main_arginfo.h b/main/main_arginfo.h index 0da355e87b77..29acd89c1c74 100644 --- a/main/main_arginfo.h +++ b/main/main_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit main.stub.php instead. - * Stub hash: 22b4c7412680888c122886bccd21e3d38953ce33 */ + * Stub hash: 11477000f37eb06d583127414783f8330e92ba8a */ #include "zend_constants.h" @@ -41,6 +41,7 @@ static void register_main_symbols(int module_number) REGISTER_LONG_CONSTANT("PHP_INT_MAX", ZEND_LONG_MAX, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_INT_MIN", ZEND_LONG_MIN, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_INT_SIZE", SIZEOF_ZEND_LONG, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PHP_SYS_SIZE", SIZEOF_SIZE_T, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_FD_SETSIZE", FD_SETSIZE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_FLOAT_DIG", DBL_DIG, CONST_PERSISTENT); REGISTER_DOUBLE_CONSTANT("PHP_FLOAT_EPSILON", DBL_EPSILON, CONST_PERSISTENT); From bac146a1c5fef16ec25b923dfe148a89c0f76515 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Wed, 9 Sep 2026 07:30:58 +0200 Subject: [PATCH 02/23] Added PHP_STRING_MAX_LENGTH from MIN(ZSTR_MAX_LEN, ZEND_LONG_MAX) The greatest length a string can have, exposed so that userland and the test suite can reason about the limit without hard coding the header size. ZSTR_MAX_LEN is SIZE_MAX - ZSTR_MAX_OVERHEAD and so exceeds ZEND_LONG_MAX wherever size_t is at least as wide as zend_long; registered unclamped it would land as a negative int. It is capped to ZEND_LONG_MAX instead, which is also the more accurate value: every userland string length travels through zend_long -- strlen()'s return value, string offsets and substr()'s arguments -- so a longer string could not report its own length. The constant therefore equals PHP_INT_MAX on every platform that ships today, including 32 bit, and only becomes a distinct value where zend_long is wider than size_t. --- main/main.stub.php | 11 +++++++++++ main/main_arginfo.h | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/main/main.stub.php b/main/main.stub.php index 63d0dc1e69ed..56719a1b7904 100644 --- a/main/main.stub.php +++ b/main/main.stub.php @@ -176,6 +176,17 @@ * @cvalue SIZEOF_SIZE_T */ const PHP_SYS_SIZE = UNKNOWN; +/** + * The greatest length a string can have. This is the allocator's limit + * (ZSTR_MAX_LEN) capped to what a zend_long can express, because every + * userland string length travels through zend_long: strlen()'s return value, + * string offsets and substr()'s arguments. Allocating a string anywhere near + * this will normally fail long before the limit itself is reached. + * + * @var int + * @cvalue (SIZEOF_SIZE_T < SIZEOF_ZEND_LONG ? (zend_long) ZSTR_MAX_LEN : ZEND_LONG_MAX) + */ +const PHP_STRING_MAX_LENGTH = UNKNOWN; /** * @var int * @cvalue FD_SETSIZE diff --git a/main/main_arginfo.h b/main/main_arginfo.h index 29acd89c1c74..920878339476 100644 --- a/main/main_arginfo.h +++ b/main/main_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit main.stub.php instead. - * Stub hash: 11477000f37eb06d583127414783f8330e92ba8a */ + * Stub hash: 9cdeba665b44478182477d3c2a20fa7343833f69 */ #include "zend_constants.h" @@ -42,6 +42,7 @@ static void register_main_symbols(int module_number) REGISTER_LONG_CONSTANT("PHP_INT_MIN", ZEND_LONG_MIN, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_INT_SIZE", SIZEOF_ZEND_LONG, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_SYS_SIZE", SIZEOF_SIZE_T, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PHP_STRING_MAX_LENGTH", (SIZEOF_SIZE_T < SIZEOF_ZEND_LONG ? (zend_long) ZSTR_MAX_LEN : ZEND_LONG_MAX), CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_FD_SETSIZE", FD_SETSIZE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_FLOAT_DIG", DBL_DIG, CONST_PERSISTENT); REGISTER_DOUBLE_CONSTANT("PHP_FLOAT_EPSILON", DBL_EPSILON, CONST_PERSISTENT); From e1d66f999cf881ac3382da401e500b48bf92ea4b Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Wed, 9 Sep 2026 20:52:55 +0200 Subject: [PATCH 03/23] Check for allocation size overflow in the debug reallocators _zend_mm_alloc() verifies that adding the per allocation zend_mm_debug_info to the requested size does not overflow, but zend_mm_realloc_heap() and zend_mm_realloc_huge() perform the same addition without that check. In a debug build a request within a few bytes of SIZE_MAX therefore wraps to a tiny allocation while the caller believes it received the size it asked for, and then writes past the end of it. The only way to reach this from PHP is assigning to a string offset close to SIZE_MAX, which is possible solely where zend_long is wider than size_t and is refused before the reallocation by the following commit, so there is no test that can exercise it. --- Zend/zend_alloc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index f6c0a1ad0e98..f4e4d8539ca1 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -1669,6 +1669,9 @@ static zend_never_inline void *zend_mm_realloc_huge(zend_mm_heap *heap, void *pt #if ZEND_DEBUG real_size = size; size = ZEND_MM_ALIGNED_SIZE(size) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info)); + if (UNEXPECTED(size < real_size)) { + zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu + %zu)", ZEND_MM_ALIGNED_SIZE(real_size), ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + } #endif if (size > ZEND_MM_MAX_LARGE_SIZE) { #if ZEND_DEBUG @@ -1774,6 +1777,9 @@ static zend_always_inline void *zend_mm_realloc_heap(zend_mm_heap *heap, void *p size_t real_size = size; size = ZEND_MM_ALIGNED_SIZE(size) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info)); + if (UNEXPECTED(size < real_size)) { + zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%zu + %zu)", ZEND_MM_ALIGNED_SIZE(real_size), ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))); + } #endif ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); From 52c74b625f5d898d289addc178391ce31a23b053 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Sat, 8 Aug 2026 07:45:17 +0200 Subject: [PATCH 04/23] Fix alignment of Bucket --- Zend/zend_types.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Zend/zend_types.h b/Zend/zend_types.h index b30f53d7f917..4c8c2fb324b1 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -383,11 +383,13 @@ struct _zend_string { char val[1]; }; -typedef struct _Bucket { +/* ZEND_BIND_* stores byte offsets into arData in opline extended values, with + * flags encoded in the low three bits. */ +typedef ZEND_SET_ALIGNED(8, struct _Bucket { zval val; zend_ulong h; /* hash value (or numeric index) */ zend_string *key; /* string key or NULL for numerics */ -} Bucket; +}) Bucket; typedef struct _zend_array HashTable; From fad55d5364f3f78d91f45103a28f3b0645ff13fa Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Mon, 7 Jul 2025 07:36:17 +0200 Subject: [PATCH 05/23] Use int64 for zend_long --- Zend/Zend.m4 | 38 ++++++++++++++ Zend/tests/str_offset_assign_sizet.phpt | 69 +++++++++++++++++++++++++ Zend/zend_alloc.c | 14 ++--- Zend/zend_compile.c | 2 +- Zend/zend_execute.c | 21 +++++--- Zend/zend_execute_API.c | 2 +- Zend/zend_generators.c | 6 +-- Zend/zend_inheritance.c | 4 +- Zend/zend_long.h | 2 +- Zend/zend_object_handlers.c | 2 +- Zend/zend_operators.h | 8 +-- Zend/zend_portability.h | 29 +++++++++++ Zend/zend_range_check.h | 27 +++++++--- Zend/zend_string.c | 5 +- Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 10 ++-- Zend/zend_vm_execute.skl | 6 +-- Zend/zend_vm_trace_map.h | 2 +- Zend/zend_weakrefs.c | 10 ++-- 19 files changed, 208 insertions(+), 51 deletions(-) create mode 100644 Zend/tests/str_offset_assign_sizet.phpt diff --git a/Zend/Zend.m4 b/Zend/Zend.m4 index 41da8344bf1f..9e280c200915 100644 --- a/Zend/Zend.m4 +++ b/Zend/Zend.m4 @@ -212,6 +212,7 @@ AX_CHECK_COMPILE_FLAG([-fno-common], [CFLAGS="-fno-common $CFLAGS"]) ZEND_CHECK_ALIGNMENT +ZEND_CHECK_INT64 ZEND_CHECK_SIGNALS ZEND_CHECK_MAX_EXECUTION_TIMERS ]) @@ -416,6 +417,43 @@ AS_VAR_IF([php_cv_align_mm], [failed], ]) ]) +dnl +dnl ZEND_CHECK_INT64 +dnl +dnl Check whether to enable 64 bit integer if supported by the system. +dnl +AC_DEFUN([ZEND_CHECK_INT64], [dnl + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[]], + [[ + #if !(defined(__x86_64__) || defined(__LP64__) || defined(_LP64) || defined(_WIN64)) + #error "Not a 64-bit platform" + #endif + ]] + )], + [ZEND_INT64=yes], + [ZEND_INT64=no]) + + AC_ARG_ENABLE([zend-int64], + [AS_HELP_STRING([--enable-zend-int64], [Enable 64bit integer support (enabled by default on 64bit arch)])], + [ZEND_INT64=$enableval], + [ZEND_INT64=$ZEND_INT64]) + + AS_VAR_IF([ZEND_INT64], [yes], + AC_CHECK_TYPE([int64_t],, + [AC_MSG_ERROR([int64_t not found])], + [#include ])) + + AS_VAR_IF([ZEND_INT64], [yes], + [AC_DEFINE([ZEND_INT64], [1], + [Define to 1 if zend_long as int64 is supported and enabled.]) + AS_VAR_APPEND([CFLAGS], [" -DZEND_INT64"])]) + + AC_MSG_CHECKING([whether to enable 64 bit integer support]) + AC_MSG_RESULT([$ZEND_INT64]) +]) + dnl dnl ZEND_CHECK_SIGNALS dnl diff --git a/Zend/tests/str_offset_assign_sizet.phpt b/Zend/tests/str_offset_assign_sizet.phpt new file mode 100644 index 000000000000..1c1ad95c9a4e --- /dev/null +++ b/Zend/tests/str_offset_assign_sizet.phpt @@ -0,0 +1,69 @@ +--TEST-- +Assigning to a string offset that would exceed the maximum string length +--INI-- +memory_limit=128M +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- + $sizeMax + 11, + 'STR_MAX' => PHP_STRING_MAX_LENGTH, + 'STR_MAX+1' => PHP_STRING_MAX_LENGTH + 1, + 'STR_MAX+2' => PHP_STRING_MAX_LENGTH + 2, + 'SIZE_MAX-2' => $sizeMax - 2, + 'SIZE_MAX-1' => $sizeMax - 1, + 'SIZE_MAX' => $sizeMax, + 'INT_MAX' => PHP_INT_MAX, +]; + +foreach ($offsets as $label => $offset) { + echo "$label: "; + $s = "abc"; + try { + $s[$offset] = 'x'; + $result = 'no error'; + } catch (Error $e) { + $result = $e::class . ': ' . $e->getMessage(); + } + echo $result, '; $s = ', var_export($s, true), "\n"; +} + +/* The largest usable offset must not be refused: it reaches the allocator and + * fails there, as it would on a build where zend_long is no wider than size_t. + * + * Which diagnostic comes back depends on the build, so only the fact that it + * got that far is asserted. ZSTR_MAX_LEN does not account for the per + * allocation zend_mm_debug_info that a debug build adds, so at the very top of + * the range a debug build reports the overflow of that addition ("Possible + * integer overflow in memory allocation") while a release build has no such + * overhead, attempts the allocation for real and reports the memory limit + * ("Allowed memory size exhausted"). */ +$s = "abc"; +$s[PHP_STRING_MAX_LENGTH - 1] = 'x'; +?> +--EXPECTF-- +SIZE_MAX+11: Error: String size overflow; $s = 'abc' +STR_MAX: Error: String size overflow; $s = 'abc' +STR_MAX+1: Error: String size overflow; $s = 'abc' +STR_MAX+2: Error: String size overflow; $s = 'abc' +SIZE_MAX-2: Error: String size overflow; $s = 'abc' +SIZE_MAX-1: Error: String size overflow; $s = 'abc' +SIZE_MAX: Error: String size overflow; $s = 'abc' +INT_MAX: Error: String size overflow; $s = 'abc' + +Fatal error: %s in %s on line %d diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index f4e4d8539ca1..5a094ab28c19 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2432,7 +2432,7 @@ static void zend_mm_check_leaks(zend_mm_heap *heap) repeated = zend_mm_find_leaks_huge(heap, list); total += 1 + repeated; if (repeated) { - zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, (void *)(uintptr_t)repeated); + zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, ZEND_ULONG_TO_PTR(repeated)); } heap->huge_list = list = list->next; @@ -2471,7 +2471,7 @@ static void zend_mm_check_leaks(zend_mm_heap *heap) zend_mm_find_leaks(heap, p, i + bin_pages[bin_num], &leak); total += 1 + repeated; if (repeated) { - zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, (void *)(uintptr_t)repeated); + zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, ZEND_ULONG_TO_PTR(repeated)); } } dbg = (zend_mm_debug_info*)((char*)dbg + bin_data_size[bin_num]); @@ -2497,7 +2497,7 @@ static void zend_mm_check_leaks(zend_mm_heap *heap) repeated = zend_mm_find_leaks(heap, p, i + pages_count, &leak); total += 1 + repeated; if (repeated) { - zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, (void *)(uintptr_t)repeated); + zend_message_dispatcher(ZMSG_MEMORY_LEAK_REPEATED, ZEND_ULONG_TO_PTR(repeated)); } i += pages_count; } @@ -3085,14 +3085,14 @@ static ZEND_COLD ZEND_NORETURN void zend_out_of_memory(void) #if ZEND_MM_CUSTOM static zend_always_inline void tracked_add(zend_mm_heap *heap, void *ptr, size_t size) { zval size_zv; - zend_ulong h = ((uintptr_t) ptr) >> ZEND_MM_ALIGNMENT_LOG2; - ZEND_ASSERT((void *) (uintptr_t) (h << ZEND_MM_ALIGNMENT_LOG2) == ptr); + zend_ulong h = ZEND_PTR_TO_ZEND_ULONG(ptr) >> ZEND_MM_ALIGNMENT_LOG2; + ZEND_ASSERT(ZEND_ULONG_TO_PTR(h << ZEND_MM_ALIGNMENT_LOG2) == ptr); ZVAL_LONG(&size_zv, size); zend_hash_index_add_new(heap->tracked_allocs, h, &size_zv); } static zend_always_inline zval *tracked_get_size_zv(zend_mm_heap *heap, void *ptr) { - zend_ulong h = ((uintptr_t) ptr) >> ZEND_MM_ALIGNMENT_LOG2; + zend_ulong h = ZEND_PTR_TO_ZEND_ULONG(ptr) >> ZEND_MM_ALIGNMENT_LOG2; zval *size_zv = zend_hash_index_find(heap->tracked_allocs, h); ZEND_ASSERT(size_zv && "Trying to free pointer not allocated through ZendMM"); return size_zv; @@ -3178,7 +3178,7 @@ static void tracked_free_all(zend_mm_heap *heap) { HashTable *tracked_allocs = heap->tracked_allocs; zend_ulong h; ZEND_HASH_FOREACH_NUM_KEY(tracked_allocs, h) { - void *ptr = (void *) (uintptr_t) (h << ZEND_MM_ALIGNMENT_LOG2); + void *ptr = ZEND_ULONG_TO_PTR(h << ZEND_MM_ALIGNMENT_LOG2); free(ptr); } ZEND_HASH_FOREACH_END(); } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 5aa1b07f304d..7c2db4ee000b 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -12966,7 +12966,7 @@ static void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */ } else if (Z_TYPE_P(dim) != IS_STRING || is_numeric_string(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &offset, NULL, 1) != IS_LONG) { return; } - if (offset < 0 || (size_t)offset >= Z_STRLEN_P(container)) { + if (offset < 0 || ZEND_SIZE_T_LTE_ZEND_LONG(Z_STRLEN_P(container), offset)) { return; } c = (uint8_t) Z_STRVAL_P(container)[offset]; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 14a340ffee37..cf1228c3cb6b 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -2160,11 +2160,20 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, } } - if ((size_t)offset >= ZSTR_LEN(s)) { + if (ZEND_SIZE_T_LTE_ZEND_LONG(ZSTR_LEN(s), offset)) { +#if SIZEOF_SIZE_T < SIZEOF_ZEND_LONG + if (UNEXPECTED(offset >= (zend_long) ZSTR_MAX_LEN)) { + zend_throw_error(NULL, "String size overflow"); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_UNDEF(EX_VAR(opline->result.var)); + } + return; + } +#endif /* Extend string if needed */ - zend_long old_len = ZSTR_LEN(s); + size_t old_len = ZSTR_LEN(s); ZVAL_NEW_STR(str, zend_string_extend(s, (size_t)offset + 1, 0)); - memset(Z_STRVAL_P(str) + old_len, ' ', offset - old_len); + memset(Z_STRVAL_P(str) + old_len, ' ', (size_t)offset - old_len); Z_STRVAL_P(str)[offset+1] = 0; } else { zend_string_forget_hash_val(Z_STR_P(str)); @@ -3150,7 +3159,7 @@ static zend_always_inline void zend_fetch_dimension_address_read(zval *result, c } out: - if (UNEXPECTED(ZSTR_LEN(str) < ((offset < 0) ? -(size_t)offset : ((size_t)offset + 1)))) { + if (UNEXPECTED(ZEND_SIZE_T_LT_ZEND_ULONG(ZSTR_LEN(str), ((offset < 0) ? -(zend_ulong)offset : ((zend_ulong)offset + 1))))) { if (type != BP_VAR_IS) { zend_error(E_WARNING, "Uninitialized string offset " ZEND_LONG_FMT, offset); ZVAL_EMPTY_STRING(result); @@ -3311,7 +3320,7 @@ static zend_never_inline bool ZEND_FASTCALL zend_isset_dim_slow(const zval *cont if (UNEXPECTED(lval < 0)) { /* Handle negative offset */ lval += (zend_long)Z_STRLEN_P(container); } - if (EXPECTED(lval >= 0) && (size_t)lval < Z_STRLEN_P(container)) { + if (EXPECTED(lval >= 0) && ZEND_SIZE_T_GT_ZEND_LONG(Z_STRLEN_P(container), lval)) { return 1; } else { return 0; @@ -3350,7 +3359,7 @@ static zend_never_inline bool ZEND_FASTCALL zend_isempty_dim_slow(const zval *co if (UNEXPECTED(lval < 0)) { /* Handle negative offset */ lval += (zend_long)Z_STRLEN_P(container); } - if (EXPECTED(lval >= 0) && (size_t)lval < Z_STRLEN_P(container)) { + if (EXPECTED(lval >= 0) && ZEND_SIZE_T_GT_ZEND_LONG(Z_STRLEN_P(container), lval)) { return (Z_STRVAL_P(container)[lval] == '0'); } else { return 1; diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index c67a31fd8de2..cf105732160a 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1245,7 +1245,7 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * ALLOC_HASHTABLE(CG(unlinked_uses)); zend_hash_init(CG(unlinked_uses), 0, NULL, NULL, 0); } - zend_hash_index_add_empty_element(CG(unlinked_uses), (zend_ulong)(uintptr_t)ce); + zend_hash_index_add_empty_element(CG(unlinked_uses), ZEND_PTR_TO_ZEND_ULONG(ce)); return ce; } return NULL; diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index b9c77e4f0f6a..d1e61130774c 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -178,7 +178,7 @@ static void zend_generator_remove_child(zend_generator_node *node, zend_generato node->child.single = NULL; } else { HashTable *ht = node->child.ht; - zend_hash_index_del(ht, (zend_ulong)(uintptr_t) child); + zend_hash_index_del(ht, ZEND_PTR_TO_ZEND_ULONG(child)); if (node->children == 2) { zend_generator *other_child; ZEND_HASH_FOREACH_PTR(ht, other_child) { @@ -553,11 +553,11 @@ static void zend_generator_add_child(zend_generator *generator, zend_generator * HashTable *ht = emalloc(sizeof(HashTable)); zend_hash_init(ht, 0, NULL, NULL, 0); zend_hash_index_add_new_ptr(ht, - (zend_ulong) node->child.single, node->child.single); + ZEND_PTR_TO_ZEND_ULONG(node->child.single), node->child.single); node->child.ht = ht; } - zend_hash_index_add_new_ptr(node->child.ht, (zend_ulong)(uintptr_t) child, child); + zend_hash_index_add_new_ptr(node->child.ht, ZEND_PTR_TO_ZEND_ULONG(child), child); } ++node->children; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 4424c9a1a3ab..5af5f6980a9d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3372,7 +3372,7 @@ static void check_unrecoverable_load_failure(const zend_class_entry *ce) { * a dependence on the inheritance hierarchy of this specific class. Instead we fall back to * a fatal error, as would happen if we did not allow exceptions in the first place. */ if (CG(unlinked_uses) - && zend_hash_index_del(CG(unlinked_uses), (zend_ulong)(uintptr_t)ce) == SUCCESS) { + && zend_hash_index_del(CG(unlinked_uses), ZEND_PTR_TO_ZEND_ULONG(ce)) == SUCCESS) { zend_exception_uncaught_error( "During inheritance of %s with variance dependencies", ZSTR_VAL(ce->name)); } @@ -3667,7 +3667,7 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string } if (CG(unlinked_uses)) { - zend_hash_index_del(CG(unlinked_uses), (zend_ulong)(uintptr_t) ce); + zend_hash_index_del(CG(unlinked_uses), ZEND_PTR_TO_ZEND_ULONG(ce)); } orig_linking_class = CG(current_linking_class); diff --git a/Zend/zend_long.h b/Zend/zend_long.h index 303bacd03d4c..8dcb6ebe8047 100644 --- a/Zend/zend_long.h +++ b/Zend/zend_long.h @@ -22,7 +22,7 @@ #include /* This is the heart of the whole int64 enablement in zval. */ -#if defined(__x86_64__) || defined(__LP64__) || defined(_LP64) || defined(_WIN64) +#ifdef ZEND_INT64 # define ZEND_ENABLE_ZVAL_LONG64 1 #endif diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 8ca6d212fd72..aa3757a184ea 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -450,7 +450,7 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c *info_ptr = property_info; } if (cache_slot) { - CACHE_POLYMORPHIC_PTR_EX(cache_slot, ce, (void*)(uintptr_t)offset); + CACHE_POLYMORPHIC_PTR_EX(cache_slot, ce, (void*) offset); CACHE_PTR_EX(cache_slot + 2, property_info); } return offset; diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index 153a6cea6b43..df0c5d002a30 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -530,11 +530,11 @@ ZEND_API void zend_reset_lc_ctype_locale(void); #define ZVAL_OFFSETOF_TYPE \ (offsetof(zval, u1.type_info) - offsetof(zval, value)) -#if defined(HAVE_ASM_GOTO) && !__has_feature(memory_sanitizer) -# define ZEND_USE_ASM_ARITHMETIC 1 -#else +//#if defined(HAVE_ASM_GOTO) && !__has_feature(memory_sanitizer) +//# define ZEND_USE_ASM_ARITHMETIC 1 +//#else # define ZEND_USE_ASM_ARITHMETIC 0 -#endif +//#endif static zend_always_inline void fast_long_increment_function(zval *op1) { diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index ccad24682fdb..36d3dd15d7c0 100644 --- a/Zend/zend_portability.h +++ b/Zend/zend_portability.h @@ -945,4 +945,33 @@ static zend_always_inline uint64_t ZEND_BYTES_SWAP64(uint64_t u) # define ZEND_OPCACHE_SHM_REATTACHMENT 1 #endif +/* A pointer always fits a zend_long on the supported platforms, + * so that direction is a plain cast. The reverse only narrows where zend_long is + * wider than a pointer, which is exactly what ZEND_INT64 on a 32bit platform + * does; the checks are compiled in only there, and only carry code in a debug + * build. Outside one ZEND_ASSERT() becomes ZEND_ASSUME(), which lets the + * optimizer drop the branch and reduce the call to the same plain cast. */ +#define ZEND_PTR_TO_ZEND_LONG(ptr) ((zend_long) (intptr_t) (ptr)) +#define ZEND_PTR_TO_ZEND_ULONG(ptr) ((zend_ulong) (uintptr_t) (ptr)) + +#if SIZEOF_SIZE_T < SIZEOF_ZEND_LONG +static zend_always_inline void* _zend_long_to_ptr(zend_long zlong) +{ + ZEND_ASSERT(zlong >= (zend_long) INTPTR_MIN && zlong <= (zend_long) INTPTR_MAX); + return (void *) (intptr_t) zlong; +} + +static zend_always_inline void* _zend_ulong_to_ptr(zend_ulong ulong) +{ + ZEND_ASSERT(ulong <= (zend_ulong) UINTPTR_MAX); + return (void *) (uintptr_t) ulong; +} + +# define ZEND_LONG_TO_PTR(zlong) _zend_long_to_ptr(zlong) +# define ZEND_ULONG_TO_PTR(ulong) _zend_ulong_to_ptr(ulong) +#else +# define ZEND_LONG_TO_PTR(zlong) ((void *) (intptr_t) (zlong)) +# define ZEND_ULONG_TO_PTR(ulong) ((void *) (uintptr_t) (ulong)) +#endif + #endif /* ZEND_PORTABILITY_H */ diff --git a/Zend/zend_range_check.h b/Zend/zend_range_check.h index 8db867434399..e6b82201dbd4 100644 --- a/Zend/zend_range_check.h +++ b/Zend/zend_range_check.h @@ -29,9 +29,7 @@ #endif #if SIZEOF_INT < SIZEOF_SIZE_T -/* size_t can always overflow signed int on the same platform. - Furthermore, by the current design, size_t can always - overflow zend_long. */ +/* size_t can always overflow signed int on the same platform. */ # define ZEND_SIZE_T_CAN_OVFL_UINT 1 #endif @@ -58,9 +56,24 @@ #endif /* Comparison zend_long vs size_t */ -#define ZEND_SIZE_T_GT_ZEND_LONG(size, zlong) ((zlong) < 0 || (size) > (size_t)(zlong)) -#define ZEND_SIZE_T_GTE_ZEND_LONG(size, zlong) ((zlong) < 0 || (size) >= (size_t)(zlong)) -#define ZEND_SIZE_T_LT_ZEND_LONG(size, zlong) ((zlong) >= 0 && (size) < (size_t)(zlong)) -#define ZEND_SIZE_T_LTE_ZEND_LONG(size, zlong) ((zlong) >= 0 && (size) <= (size_t)(zlong)) +#if SIZEOF_SIZE_T < SIZEOF_ZEND_LONG +# define ZEND_SIZE_T_GT_ZEND_LONG(size, zlong) ((zlong) < 0 || ((zlong) < SIZE_MAX && (size) > (size_t)(zlong))) +# define ZEND_SIZE_T_GT_ZEND_ULONG(size, zulong) (zulong < SIZE_MAX && (size) > (size_t)(zulong)) +# define ZEND_SIZE_T_GTE_ZEND_LONG(size, zlong) ((zlong) < 0 || ((zlong) <= SIZE_MAX && (size) >= (size_t)(zlong))) +# define ZEND_SIZE_T_GTE_ZEND_ULONG(size, zulong) ((zulong) <= SIZE_MAX && (size) >= (size_t)(zulong)) +# define ZEND_SIZE_T_LT_ZEND_LONG(size, zlong) ((zlong) >= SIZE_MAX || ((zlong) > 0 && (size) < (size_t)(zlong))) +# define ZEND_SIZE_T_LT_ZEND_ULONG(size, zulong) ((zulong) >= SIZE_MAX || (size) < (size_t)(zulong)) +# define ZEND_SIZE_T_LTE_ZEND_LONG(size, zlong) ((zlong) > SIZE_MAX || ((zlong) >= 0 && (size) <= (size_t)(zlong))) +# define ZEND_SIZE_T_LTE_ZEND_ULONG(size, zulong) ((zulong) > SIZE_MAX || (size) <= (size_t)(zlong)) +#else +# define ZEND_SIZE_T_GT_ZEND_LONG(size, zlong) ((zlong) < 0 || (size) > (size_t)(zlong)) +# define ZEND_SIZE_T_GT_ZEND_ULONG(size, zulong) ((size) > (size_t)(zulong)) +# define ZEND_SIZE_T_GTE_ZEND_LONG(size, zlong) ((zlong) < 0 || (size) >= (size_t)(zlong)) +# define ZEND_SIZE_T_GTE_ZEND_ULONG(size, zulong) ((size) >= (size_t)(zulong)) +# define ZEND_SIZE_T_LT_ZEND_LONG(size, zlong) ((zlong) > 0 && (size) < (size_t)(zlong)) +# define ZEND_SIZE_T_LT_ZEND_ULONG(size, zulong) ((size) < (size_t)(zulong)) +# define ZEND_SIZE_T_LTE_ZEND_LONG(size, zlong) ((zlong) >= 0 && (size) <= (size_t)(zlong)) +# define ZEND_SIZE_T_LTE_ZEND_ULONG(size, zulong) ((size) <= (size_t)(zulong)) +#endif #endif /* ZEND_RANGE_CHECK_H */ diff --git a/Zend/zend_string.c b/Zend/zend_string.c index b1b4a0e17a69..ea4deac2f536 100644 --- a/Zend/zend_string.c +++ b/Zend/zend_string.c @@ -425,7 +425,7 @@ ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const const char *ptr = ZSTR_VAL(s1); uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1; size_t len = ZSTR_LEN(s1); - zend_ulong ret; + size_t ret; __asm__ ( "0:\n\t" @@ -456,14 +456,13 @@ ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const : "cc"); return ret; } - #elif defined(__GNUC__) && defined(__x86_64__) && !defined(__ILP32__) ZEND_API zend_never_inline NOIPA bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2) { const char *ptr = ZSTR_VAL(s1); uintptr_t delta = (uintptr_t) s2 - (uintptr_t) s1; size_t len = ZSTR_LEN(s1); - zend_ulong ret; + size_t ret; __asm__ ( "0:\n\t" diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index c0fd2eef277b..ef0223fce80b 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9853,7 +9853,7 @@ ZEND_VM_HANDLER(202, ZEND_CALLABLE_CONVERT, UNUSED, UNUSED, NUM|CACHE_SLOT) } else { /* Rotate the key for better hash distribution. */ const int shift = sizeof(size_t) == 4 ? 6 : 7; - zend_ulong key = (zend_ulong)(uintptr_t)call->func; + zend_ulong key = ZEND_PTR_TO_ZEND_ULONG(call->func); key = (key >> shift) | (key << ((sizeof(key) * 8) - shift)); zval *closure_zv = zend_hash_index_lookup(&EG(callable_convert_cache), key); if (Z_TYPE_P(closure_zv) == IS_NULL) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index b956b638b7e4..5986d43cd634 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -37662,7 +37662,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV } else { /* Rotate the key for better hash distribution. */ const int shift = sizeof(size_t) == 4 ? 6 : 7; - zend_ulong key = (zend_ulong)(uintptr_t)call->func; + zend_ulong key = ZEND_PTR_TO_ZEND_ULONG(call->func); key = (key >> shift) | (key << ((sizeof(key) * 8) - shift)); zval *closure_zv = zend_hash_index_lookup(&EG(callable_convert_cache), key); if (Z_TYPE_P(closure_zv) == IS_NULL) { @@ -90305,7 +90305,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_S } else { /* Rotate the key for better hash distribution. */ const int shift = sizeof(size_t) == 4 ? 6 : 7; - zend_ulong key = (zend_ulong)(uintptr_t)call->func; + zend_ulong key = ZEND_PTR_TO_ZEND_ULONG(call->func); key = (key >> shift) | (key << ((sizeof(key) * 8) - shift)); zval *closure_zv = zend_hash_index_lookup(&EG(callable_convert_cache), key); if (Z_TYPE_P(closure_zv) == IS_NULL) { @@ -123271,7 +123271,7 @@ static void init_opcode_serialiser(void) Z_TYPE_INFO(tmp) = IS_LONG; for (i = 0; i < zend_handlers_count; i++) { Z_LVAL(tmp) = i; - zend_hash_index_add(zend_handlers_table, (zend_ulong)(uintptr_t)zend_opcode_handlers[i], &tmp); + zend_hash_index_add(zend_handlers_table, ZEND_PTR_TO_ZEND_ULONG(zend_opcode_handlers[i]), &tmp); } } @@ -123282,7 +123282,7 @@ ZEND_API void ZEND_FASTCALL zend_serialize_opcode_handler(zend_op *op) if (!zend_handlers_table) { init_opcode_serialiser(); } - zv = zend_hash_index_find(zend_handlers_table, (zend_ulong)(uintptr_t)op->handler); + zv = zend_hash_index_find(zend_handlers_table, ZEND_PTR_TO_ZEND_ULONG(op->handler)); ZEND_ASSERT(zv != NULL); op->handler = (zend_vm_opcode_handler_t)(uintptr_t)Z_LVAL_P(zv); } @@ -123300,7 +123300,7 @@ ZEND_API const void* ZEND_FASTCALL zend_get_opcode_handler_func(const zend_op *o if (!zend_handlers_table) { init_opcode_serialiser(); } - zv = zend_hash_index_find(zend_handlers_table, (zend_ulong)(uintptr_t)op->handler); + zv = zend_hash_index_find(zend_handlers_table, ZEND_PTR_TO_ZEND_ULONG(op->handler)); ZEND_ASSERT(zv != NULL); return zend_opcode_handler_funcs[Z_LVAL_P(zv)]; #elif ZEND_VM_KIND == ZEND_VM_KIND_CALL diff --git a/Zend/zend_vm_execute.skl b/Zend/zend_vm_execute.skl index 4e8f28270bae..e0f0bbe5e244 100644 --- a/Zend/zend_vm_execute.skl +++ b/Zend/zend_vm_execute.skl @@ -114,7 +114,7 @@ static void init_opcode_serialiser(void) Z_TYPE_INFO(tmp) = IS_LONG; for (i = 0; i < zend_handlers_count; i++) { Z_LVAL(tmp) = i; - zend_hash_index_add(zend_handlers_table, (zend_ulong)(uintptr_t)zend_opcode_handlers[i], &tmp); + zend_hash_index_add(zend_handlers_table, ZEND_PTR_TO_ZEND_ULONG(zend_opcode_handlers[i]), &tmp); } } @@ -125,7 +125,7 @@ ZEND_API void ZEND_FASTCALL zend_serialize_opcode_handler(zend_op *op) if (!zend_handlers_table) { init_opcode_serialiser(); } - zv = zend_hash_index_find(zend_handlers_table, (zend_ulong)(uintptr_t)op->handler); + zv = zend_hash_index_find(zend_handlers_table, ZEND_PTR_TO_ZEND_ULONG(op->handler)); ZEND_ASSERT(zv != NULL); op->handler = (zend_vm_opcode_handler_t)(uintptr_t)Z_LVAL_P(zv); } @@ -143,7 +143,7 @@ ZEND_API const void* ZEND_FASTCALL zend_get_opcode_handler_func(const zend_op *o if (!zend_handlers_table) { init_opcode_serialiser(); } - zv = zend_hash_index_find(zend_handlers_table, (zend_ulong)(uintptr_t)op->handler); + zv = zend_hash_index_find(zend_handlers_table, ZEND_PTR_TO_ZEND_ULONG(op->handler)); ZEND_ASSERT(zv != NULL); return zend_opcode_handler_funcs[Z_LVAL_P(zv)]; #elif ZEND_VM_KIND == ZEND_VM_KIND_CALL diff --git a/Zend/zend_vm_trace_map.h b/Zend/zend_vm_trace_map.h index 18c705b454d5..bf0abddcf474 100644 --- a/Zend/zend_vm_trace_map.h +++ b/Zend/zend_vm_trace_map.h @@ -19,7 +19,7 @@ #include "zend_sort.h" #define GEN_MAP(n, name) do { \ - ZVAL_LONG(&tmp, (zend_long)(uintptr_t)zend_opcode_handlers[n]); \ + ZVAL_LONG(&tmp, ZEND_PTR_TO_ZEND_ULONG(zend_opcode_handlers[n])); \ zend_hash_str_add(&vm_trace_ht, #name, sizeof(#name) - 1, &tmp); \ } while (0); diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index ad0308f44e20..5bee3f0838cd 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -107,15 +107,15 @@ static void zend_weakref_register(zend_object *object, void *payload) { void *tagged_ptr = Z_PTR_P(zv); if (ZEND_WEAKREF_GET_TAG(tagged_ptr) == ZEND_WEAKREF_TAG_HT) { HashTable *ht = ZEND_WEAKREF_GET_PTR(tagged_ptr); - zend_hash_index_add_new_ptr(ht, (zend_ulong)(uintptr_t) payload, payload); + zend_hash_index_add_new_ptr(ht, ZEND_PTR_TO_ZEND_ULONG(payload), payload); return; } /* Convert simple pointer to hashtable. */ HashTable *ht = emalloc(sizeof(HashTable)); zend_hash_init(ht, 0, NULL, NULL, 0); - zend_hash_index_add_new_ptr(ht, (zend_ulong)(uintptr_t) tagged_ptr, tagged_ptr); - zend_hash_index_add_new_ptr(ht, (zend_ulong)(uintptr_t) payload, payload); + zend_hash_index_add_new_ptr(ht, ZEND_PTR_TO_ZEND_ULONG(tagged_ptr), tagged_ptr); + zend_hash_index_add_new_ptr(ht, ZEND_PTR_TO_ZEND_ULONG(payload), payload); /* Replace the single WeakMap or WeakReference entry in EG(weakrefs) with a HashTable with 2 entries in place. */ ZVAL_PTR(zv, ZEND_WEAKREF_ENCODE(ht, ZEND_WEAKREF_TAG_HT)); } @@ -144,11 +144,11 @@ static void zend_weakref_unregister(zend_object *object, void *payload, bool wea HashTable *ht = ptr; #if ZEND_DEBUG - void *old_payload = zend_hash_index_find_ptr(ht, (zend_ulong)(uintptr_t) payload); + void *old_payload = zend_hash_index_find_ptr(ht, ZEND_PTR_TO_ZEND_ULONG(payload)); ZEND_ASSERT(old_payload && "Weakref not registered?"); ZEND_ASSERT(old_payload == payload); #endif - zend_hash_index_del(ht, (zend_ulong)(uintptr_t) payload); + zend_hash_index_del(ht, ZEND_PTR_TO_ZEND_ULONG(payload)); if (zend_hash_num_elements(ht) == 0) { GC_DEL_FLAGS(object, IS_OBJ_WEAKLY_REFERENCED); zend_hash_destroy(ht); From 3cc74c5d2718e1bb8bd085dd0e8d1f1934c7b66c Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Fri, 11 Sep 2026 20:07:30 +0200 Subject: [PATCH 06/23] Use size_t rather than zend_long in the memory manager The allocator works in units of the platform word, not of zend_long. The two coincide everywhere except with ZEND_INT64 on a 32bit platform, where the page bitset became 64bit wide and every scan of it did 64bit arithmetic on 32bit registers. Type the bitset as size_t, move the two width checks it feeds onto SIZEOF_SIZE_T, and build its masks from zend_mm_bitset rather than from Z_UL()/Z_L(). heap->limit is a size_t as well and no longer takes a detour through zend_long to spell (size_t)-1. zend_mm_bitset_ntz() replaces the call to zend_ulong_ntz(), which is part of the zend_bitset family and typed for zend_ulong; a size_t bitset widened into it on every page scan. It carries the implementation and zend_mm_bitset_nts() is now ntz() of the complement, so the two cannot drift apart. The runtime sizeof() test inside the 8 byte branch of the fallback is gone; the enclosing #if already decides it. --- Zend/zend_alloc.c | 60 +++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 5a094ab28c19..7abef47317ac 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -173,7 +173,7 @@ static size_t _real_page_size = ZEND_MM_PAGE_SIZE; #endif typedef uint32_t zend_mm_page_info; /* 4-byte integer */ -typedef zend_ulong zend_mm_bitset; /* 4-byte or 8-byte integer */ +typedef size_t zend_mm_bitset; /* 4-byte or 8-byte integer */ #define ZEND_MM_ALIGNED_OFFSET(size, alignment) \ (((size_t)(size)) & ((alignment) - 1)) @@ -568,20 +568,20 @@ static void *zend_mm_mmap(size_t size) /* Bitmask */ /***********/ -/* number of trailing set (1) bits */ -ZEND_ATTRIBUTE_CONST static zend_always_inline int zend_mm_bitset_nts(zend_mm_bitset bitset) +/* number of trailing zero (0) bits */ +ZEND_ATTRIBUTE_CONST static zend_always_inline int zend_mm_bitset_ntz(zend_mm_bitset bitset) { -#if (defined(__GNUC__) || __has_builtin(__builtin_ctzl)) && SIZEOF_ZEND_LONG == SIZEOF_LONG && defined(PHP_HAVE_BUILTIN_CTZL) - return __builtin_ctzl(~bitset); +#if (defined(__GNUC__) || __has_builtin(__builtin_ctzl)) && SIZEOF_SIZE_T == SIZEOF_LONG && defined(PHP_HAVE_BUILTIN_CTZL) + return __builtin_ctzl(bitset); #elif (defined(__GNUC__) || __has_builtin(__builtin_ctzll)) && defined(PHP_HAVE_BUILTIN_CTZLL) - return __builtin_ctzll(~bitset); + return __builtin_ctzll(bitset); #elif defined(_WIN32) unsigned long index; #if defined(_WIN64) - if (!BitScanForward64(&index, ~bitset)) { + if (!BitScanForward64(&index, bitset)) { #else - if (!BitScanForward(&index, ~bitset)) { + if (!BitScanForward(&index, bitset)) { #endif /* undefined behavior */ return 32; @@ -591,22 +591,26 @@ ZEND_ATTRIBUTE_CONST static zend_always_inline int zend_mm_bitset_nts(zend_mm_bi #else int n; - if (bitset == (zend_mm_bitset)-1) return ZEND_MM_BITSET_LEN; + if (bitset == 0) return ZEND_MM_BITSET_LEN; n = 0; -#if SIZEOF_ZEND_LONG == 8 - if (sizeof(zend_mm_bitset) == 8) { - if ((bitset & 0xffffffff) == 0xffffffff) {n += 32; bitset = bitset >> Z_UL(32);} - } +#if SIZEOF_SIZE_T == 8 + if ((bitset & 0xffffffff) == 0) {n += 32; bitset = bitset >> 32;} #endif - if ((bitset & 0x0000ffff) == 0x0000ffff) {n += 16; bitset = bitset >> 16;} - if ((bitset & 0x000000ff) == 0x000000ff) {n += 8; bitset = bitset >> 8;} - if ((bitset & 0x0000000f) == 0x0000000f) {n += 4; bitset = bitset >> 4;} - if ((bitset & 0x00000003) == 0x00000003) {n += 2; bitset = bitset >> 2;} - return n + (bitset & 1); + if ((bitset & 0x0000ffff) == 0) {n += 16; bitset = bitset >> 16;} + if ((bitset & 0x000000ff) == 0) {n += 8; bitset = bitset >> 8;} + if ((bitset & 0x0000000f) == 0) {n += 4; bitset = bitset >> 4;} + if ((bitset & 0x00000003) == 0) {n += 2; bitset = bitset >> 2;} + return n + !(bitset & 1); #endif } +/* number of trailing set (1) bits */ +ZEND_ATTRIBUTE_CONST static zend_always_inline int zend_mm_bitset_nts(zend_mm_bitset bitset) +{ + return zend_mm_bitset_ntz(~bitset); +} + static zend_always_inline int zend_mm_bitset_is_set(zend_mm_bitset *bitset, int bit) { return ZEND_BIT_TEST(bitset, bit); @@ -614,12 +618,12 @@ static zend_always_inline int zend_mm_bitset_is_set(zend_mm_bitset *bitset, int static zend_always_inline void zend_mm_bitset_set_bit(zend_mm_bitset *bitset, int bit) { - bitset[bit / ZEND_MM_BITSET_LEN] |= (Z_UL(1) << (bit & (ZEND_MM_BITSET_LEN-1))); + bitset[bit / ZEND_MM_BITSET_LEN] |= ((zend_mm_bitset)1 << (bit & (ZEND_MM_BITSET_LEN-1))); } static zend_always_inline void zend_mm_bitset_reset_bit(zend_mm_bitset *bitset, int bit) { - bitset[bit / ZEND_MM_BITSET_LEN] &= ~(Z_UL(1) << (bit & (ZEND_MM_BITSET_LEN-1))); + bitset[bit / ZEND_MM_BITSET_LEN] &= ~((zend_mm_bitset)1 << (bit & (ZEND_MM_BITSET_LEN-1))); } static zend_always_inline void zend_mm_bitset_set_range(zend_mm_bitset *bitset, int start, int len) @@ -666,7 +670,7 @@ static zend_always_inline void zend_mm_bitset_reset_range(zend_mm_bitset *bitset if (pos != end) { /* reset bits from "bit" to ZEND_MM_BITSET_LEN-1 */ - tmp = ~((Z_UL(1) << bit) - 1); + tmp = ~(((zend_mm_bitset)1 << bit) - 1); bitset[pos++] &= ~tmp; while (pos != end) { /* set all bits */ @@ -1013,7 +1017,7 @@ static void *zend_mm_alloc_pages(zend_mm_heap *heap, uint32_t pages_count ZEND_F tmp = *(bitset++); } /* find first 1 bit */ - len = (i + zend_ulong_ntz(tmp)) - page_num; + len = (i + zend_mm_bitset_ntz(tmp)) - page_num; if (len >= pages_count) { goto found; } @@ -1070,7 +1074,7 @@ static void *zend_mm_alloc_pages(zend_mm_heap *heap, uint32_t pages_count ZEND_F tmp = *(bitset++); } /* find first 1 bit */ - len = i + zend_ulong_ntz(tmp) - page_num; + len = i + zend_mm_bitset_ntz(tmp) - page_num; if (len >= pages_count) { if (len == pages_count) { goto found; @@ -2160,7 +2164,7 @@ static zend_mm_heap *zend_mm_init(void) chunk->free_pages = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE; chunk->free_tail = ZEND_MM_FIRST_PAGE; chunk->num = 0; - chunk->free_map[0] = (Z_L(1) << ZEND_MM_FIRST_PAGE) - 1; + chunk->free_map[0] = (1L << ZEND_MM_FIRST_PAGE) - 1; chunk->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE); heap->main_chunk = chunk; heap->cached_chunks = NULL; @@ -2180,7 +2184,7 @@ static zend_mm_heap *zend_mm_init(void) #endif zend_mm_init_key(heap); #if ZEND_MM_LIMIT - heap->limit = (size_t)Z_L(-1) >> 1; + heap->limit = (size_t)-1 >> 1; heap->overflow = 0; #endif #if ZEND_MM_CUSTOM @@ -3389,7 +3393,7 @@ static void alloc_globals_ctor(zend_alloc_globals *alloc_globals) zend_mm_heap *mm_heap = alloc_globals->mm_heap = malloc(sizeof(zend_mm_heap)); memset(mm_heap, 0, sizeof(zend_mm_heap)); mm_heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_STD; - mm_heap->limit = (size_t)Z_L(-1) >> 1; + mm_heap->limit = (size_t)-1 >> 1; mm_heap->overflow = 0; if (!tracked) { @@ -3592,7 +3596,7 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void chunk->free_pages = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE; chunk->free_tail = ZEND_MM_FIRST_PAGE; chunk->num = 0; - chunk->free_map[0] = (Z_L(1) << ZEND_MM_FIRST_PAGE) - 1; + chunk->free_map[0] = (1L << ZEND_MM_FIRST_PAGE) - 1; chunk->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE); heap->main_chunk = chunk; heap->cached_chunks = NULL; @@ -3612,7 +3616,7 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void #endif zend_mm_init_key(heap); #if ZEND_MM_LIMIT - heap->limit = (size_t)Z_L(-1) >> 1; + heap->limit = (size_t)-1 >> 1; heap->overflow = 0; #endif #if ZEND_MM_CUSTOM From b329c8fd31702d08ccb9f6aa2db6493367d8a4f6 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Wed, 9 Sep 2026 23:56:23 +0200 Subject: [PATCH 07/23] Re-enable ZEND_USE_ASM_ARITHMETIC The previous commit commented the whole optimisation out because it did not work on i386 with a 64-bit zend_long. That switched it off on every platform, including x86-64 and aarch64 where the sequences were always correct, so narrow it down to what is actually broken. The i386 sequences operate on 32 bits: addl/subl on the low half of the value and jo on a 32-bit overflow flag. With a 64-bit zend_long that is wrong three ways -- 2147483647 + 1 reports an overflow that did not happen, 4294967295 + 1 drops the carry and yields 0, and PHP_INT_MAX + 1 overflows unnoticed and yields 9223372032559808512. Increment and decrement gain a 64-bit sequence that carries across both halves with adcl/sbbl, where the overflow flag of the second instruction is the one that matters. Addition and subtraction keep the 32-bit sequence only: holding a 64-bit value needs a second scratch register, and inside execute_ex() %esi and %edi are pinned as the VM's global register variables, leaving eax/ebx/ecx/edx. Three "r" operands plus two clobbers cannot be allocated, and gcc loops in register allocation rather than reporting it. Those two are served by the __builtin_s{add,sub}ll_overflow() paths that follow. int_overflow_64bit.phpt covers the increment and decrement boundaries, which nothing exercised before. --- Zend/tests/int_overflow_64bit.phpt | 17 ++++++++++ Zend/zend_operators.h | 54 +++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/Zend/tests/int_overflow_64bit.phpt b/Zend/tests/int_overflow_64bit.phpt index cc14e14949ca..47aaa3c17262 100644 --- a/Zend/tests/int_overflow_64bit.phpt +++ b/Zend/tests/int_overflow_64bit.phpt @@ -20,6 +20,17 @@ foreach ($doubles as $d) { var_dump($l); } +/* ++ and -- overflow to float at the zend_long boundary, and must not do so + * anywhere below it. The 32-bit boundaries matter on builds where zend_long is + * wider than the platform word: they are the values a 32-bit increment would + * either wrap or wrongly report as overflowing. */ +$i = PHP_INT_MAX; $i++; var_dump($i); +$i = PHP_INT_MIN; $i--; var_dump($i); +$i = 2147483647; $i++; var_dump($i); +$i = 4294967295; $i++; var_dump($i); +$i = -2147483648; $i--; var_dump($i); +$i = -4294967296; $i--; var_dump($i); + echo "Done\n"; ?> --EXPECTF-- @@ -36,4 +47,10 @@ int(0) int(-9223372036854775808) int(-9223372036854775808) int(-9223372036854775808) +float(9.223372036854776E+18) +float(-9.223372036854776E+18) +int(2147483648) +int(4294967296) +int(-2147483649) +int(-4294967297) Done diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index df0c5d002a30..0a79ffc5b5ac 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -530,22 +530,34 @@ ZEND_API void zend_reset_lc_ctype_locale(void); #define ZVAL_OFFSETOF_TYPE \ (offsetof(zval, u1.type_info) - offsetof(zval, value)) -//#if defined(HAVE_ASM_GOTO) && !__has_feature(memory_sanitizer) -//# define ZEND_USE_ASM_ARITHMETIC 1 -//#else +#if defined(HAVE_ASM_GOTO) && !__has_feature(memory_sanitizer) +# define ZEND_USE_ASM_ARITHMETIC 1 +#else # define ZEND_USE_ASM_ARITHMETIC 0 -//#endif +#endif static zend_always_inline void fast_long_increment_function(zval *op1) { -#if ZEND_USE_ASM_ARITHMETIC && defined(__i386__) && !(4 == __GNUC__ && 8 == __GNUC_MINOR__) +#if ZEND_USE_ASM_ARITHMETIC && defined(__i386__) \ + && (SIZEOF_ZEND_LONG == 4 || SIZEOF_ZEND_LONG == 8) && !(4 == __GNUC__ && 8 == __GNUC_MINOR__) +# if SIZEOF_ZEND_LONG == 4 + __asm__ goto( + "addl $1,(%0)\n\t" + "jo %l1\n" + : + : "r"(&op1->value) + : "cc", "memory" + : overflow); +# else __asm__ goto( "addl $1,(%0)\n\t" + "adcl $0,4(%0)\n\t" "jo %l1\n" : : "r"(&op1->value) : "cc", "memory" : overflow); +# endif return; overflow: ZEND_ATTRIBUTE_COLD_LABEL ZVAL_DOUBLE(op1, (double)ZEND_LONG_MAX + 1.0); @@ -617,7 +629,9 @@ overflow: ZEND_ATTRIBUTE_COLD_LABEL static zend_always_inline void fast_long_decrement_function(zval *op1) { -#if ZEND_USE_ASM_ARITHMETIC && defined(__i386__) && !(4 == __GNUC__ && 8 == __GNUC_MINOR__) +#if ZEND_USE_ASM_ARITHMETIC && defined(__i386__) \ + && (SIZEOF_ZEND_LONG == 4 || SIZEOF_ZEND_LONG == 8) && !(4 == __GNUC__ && 8 == __GNUC_MINOR__) +# if SIZEOF_ZEND_LONG == 4 __asm__ goto( "subl $1,(%0)\n\t" "jo %l1\n" @@ -625,6 +639,16 @@ static zend_always_inline void fast_long_decrement_function(zval *op1) : "r"(&op1->value) : "cc", "memory" : overflow); +# else + __asm__ goto( + "subl $1,(%0)\n\t" + "sbbl $0,4(%0)\n\t" + "jo %l1\n" + : + : "r"(&op1->value) + : "cc", "memory" + : overflow); +# endif return; overflow: ZEND_ATTRIBUTE_COLD_LABEL ZVAL_DOUBLE(op1, (double)ZEND_LONG_MIN - 1.0); @@ -696,7 +720,14 @@ overflow: ZEND_ATTRIBUTE_COLD_LABEL static zend_always_inline void fast_long_add_function(zval *result, zval *op1, zval *op2) { -#if ZEND_USE_ASM_ARITHMETIC && defined(__i386__) && !(4 == __GNUC__ && 8 == __GNUC_MINOR__) +/* There is no SIZEOF_ZEND_LONG == 8 counterpart to the i386 sequence below: + * holding a 64-bit value takes two scratch registers, and inside execute_ex() + * %esi and %edi are pinned as the VM's global register variables, leaving only + * eax/ebx/ecx/edx. Three "r" operands plus two clobbers cannot be satisfied, + * and gcc loops in register allocation instead of reporting it. That + * configuration is served by the __builtin_s{add,sub}ll_overflow() paths + * further down. */ +#if ZEND_USE_ASM_ARITHMETIC && defined(__i386__) && SIZEOF_ZEND_LONG == 4 && !(4 == __GNUC__ && 8 == __GNUC_MINOR__) __asm__ goto( "movl (%1), %%eax\n\t" "addl (%2), %%eax\n\t" @@ -800,7 +831,14 @@ overflow: ZEND_ATTRIBUTE_COLD_LABEL static zend_always_inline void fast_long_sub_function(zval *result, zval *op1, zval *op2) { -#if ZEND_USE_ASM_ARITHMETIC && defined(__i386__) && !(4 == __GNUC__ && 8 == __GNUC_MINOR__) +/* There is no SIZEOF_ZEND_LONG == 8 counterpart to the i386 sequence below: + * holding a 64-bit value takes two scratch registers, and inside execute_ex() + * %esi and %edi are pinned as the VM's global register variables, leaving only + * eax/ebx/ecx/edx. Three "r" operands plus two clobbers cannot be satisfied, + * and gcc loops in register allocation instead of reporting it. That + * configuration is served by the __builtin_s{add,sub}ll_overflow() paths + * further down. */ +#if ZEND_USE_ASM_ARITHMETIC && defined(__i386__) && SIZEOF_ZEND_LONG == 4 && !(4 == __GNUC__ && 8 == __GNUC_MINOR__) __asm__ goto( "movl (%1), %%eax\n\t" "subl (%2), %%eax\n\t" From 796d79ebdbef04a2e24eba991b91756bbf681020 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Fri, 14 Aug 2026 20:04:39 +0200 Subject: [PATCH 08/23] Added range checks helpers zend_long vs. size_t and zend_string length --- Zend/zend_range_check.h | 25 ++++++++++++++++++++----- Zend/zend_string.h | 16 ++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/Zend/zend_range_check.h b/Zend/zend_range_check.h index e6b82201dbd4..0f9b67d61470 100644 --- a/Zend/zend_range_check.h +++ b/Zend/zend_range_check.h @@ -55,17 +55,23 @@ # define ZEND_SIZE_T_UINT_OVFL(size) (0) #endif -/* Comparison zend_long vs size_t */ +/* zend_long vs size_t checks. */ #if SIZEOF_SIZE_T < SIZEOF_ZEND_LONG +# define ZEND_SIZE_T_ZEND_LONG_OVFL(size) (0) +# define ZEND_LONG_SIZE_T_OVFL(zlong) UNEXPECTED((zlong) > (zend_long)SIZE_MAX) + # define ZEND_SIZE_T_GT_ZEND_LONG(size, zlong) ((zlong) < 0 || ((zlong) < SIZE_MAX && (size) > (size_t)(zlong))) -# define ZEND_SIZE_T_GT_ZEND_ULONG(size, zulong) (zulong < SIZE_MAX && (size) > (size_t)(zulong)) +# define ZEND_SIZE_T_GT_ZEND_ULONG(size, zulong) ((zulong) < SIZE_MAX && (size) > (size_t)(zulong)) # define ZEND_SIZE_T_GTE_ZEND_LONG(size, zlong) ((zlong) < 0 || ((zlong) <= SIZE_MAX && (size) >= (size_t)(zlong))) # define ZEND_SIZE_T_GTE_ZEND_ULONG(size, zulong) ((zulong) <= SIZE_MAX && (size) >= (size_t)(zulong)) -# define ZEND_SIZE_T_LT_ZEND_LONG(size, zlong) ((zlong) >= SIZE_MAX || ((zlong) > 0 && (size) < (size_t)(zlong))) -# define ZEND_SIZE_T_LT_ZEND_ULONG(size, zulong) ((zulong) >= SIZE_MAX || (size) < (size_t)(zulong)) +# define ZEND_SIZE_T_LT_ZEND_LONG(size, zlong) ((zlong) > SIZE_MAX || ((zlong) > 0 && (size) < (size_t)(zlong))) +# define ZEND_SIZE_T_LT_ZEND_ULONG(size, zulong) ((zulong) > SIZE_MAX || (size) < (size_t)(zulong)) # define ZEND_SIZE_T_LTE_ZEND_LONG(size, zlong) ((zlong) > SIZE_MAX || ((zlong) >= 0 && (size) <= (size_t)(zlong))) -# define ZEND_SIZE_T_LTE_ZEND_ULONG(size, zulong) ((zulong) > SIZE_MAX || (size) <= (size_t)(zlong)) +# define ZEND_SIZE_T_LTE_ZEND_ULONG(size, zulong) ((zulong) > SIZE_MAX || (size) <= (size_t)(zulong)) #else +# define ZEND_SIZE_T_ZEND_LONG_OVFL(size) UNEXPECTED((size) > (size_t)ZEND_LONG_MAX) +# define ZEND_LONG_SIZE_T_OVFL(zlong) (0) + # define ZEND_SIZE_T_GT_ZEND_LONG(size, zlong) ((zlong) < 0 || (size) > (size_t)(zlong)) # define ZEND_SIZE_T_GT_ZEND_ULONG(size, zulong) ((size) > (size_t)(zulong)) # define ZEND_SIZE_T_GTE_ZEND_LONG(size, zlong) ((zlong) < 0 || (size) >= (size_t)(zlong)) @@ -76,4 +82,13 @@ # define ZEND_SIZE_T_LTE_ZEND_ULONG(size, zulong) ((size) <= (size_t)(zulong)) #endif +# define ZEND_LONG_GT_SIZE_T(zlong, size) ZEND_SIZE_T_LT_ZEND_LONG(size, zlong) +# define ZEND_ULONG_GT_SIZE_T(zulong, size) ZEND_SIZE_T_LT_ZEND_ULONG(size, zulong) +# define ZEND_LONG_GTE_SIZE_T(zlong, size) ZEND_SIZE_T_LTE_ZEND_LONG(size, zlong) +# define ZEND_ULONG_GTE_SIZE_T(zulong, size) ZEND_SIZE_T_LTE_ZEND_ULONG(size, zulong) +# define ZEND_LONG_LT_SIZE_T(zlong, size) ZEND_SIZE_T_GT_ZEND_LONG(size, zlong) +# define ZEND_ULONG_LT_SIZE_T(zulong, size) ZEND_SIZE_T_GT_ZEND_ULONG(size, zulong) +# define ZEND_LONG_LTE_SIZE_T(zlong, size) ZEND_SIZE_T_GTE_ZEND_LONG(size, zlong) +# define ZEND_ULONG_LTE_SIZE_T(zulong, size) ZEND_SIZE_T_GTE_ZEND_ULONG(size, zulong) + #endif /* ZEND_RANGE_CHECK_H */ diff --git a/Zend/zend_string.h b/Zend/zend_string.h index ad66fe168c8e..a538b70c2c49 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -130,6 +130,22 @@ static zend_always_inline zend_string *ZSTR_KNOWN(size_t idx) { #define ZSTR_MAX_OVERHEAD (ZEND_MM_ALIGNED_SIZE(_ZSTR_HEADER_SIZE + 1)) #define ZSTR_MAX_LEN (SIZE_MAX - ZSTR_MAX_OVERHEAD) +/* True when a zend_long is too large to be used as a zend_string length. + * zend_string_alloc() and zend_string_safe_alloc() add the header and the + * terminating NUL to the requested length without checking for overflow, so a + * length above ZSTR_MAX_LEN wraps to a tiny allocation carrying a huge + * ZSTR_LEN. Callers must reject negative values separately. + * + * Always false where zend_long is no wider than size_t: a non-negative + * zend_long cannot exceed ZSTR_MAX_LEN there. Note that the same is not true + * of a size_t operand, which is why plain comparisons against ZSTR_MAX_LEN are + * used for those. */ +#if SIZEOF_SIZE_T < SIZEOF_ZEND_LONG +# define ZEND_LONG_ZSTR_LEN_OVFL(zlong) UNEXPECTED((zlong) > (zend_long) ZSTR_MAX_LEN) +#else +# define ZEND_LONG_ZSTR_LEN_OVFL(zlong) (0) +#endif + #define ZSTR_ALLOCA_ALLOC(str, _len, use_heap) do { \ (str) = (zend_string *)do_alloca(ZEND_MM_ALIGNED_SIZE_EX(_ZSTR_STRUCT_SIZE(_len), 8), (use_heap)); \ GC_SET_REFCOUNT(str, 1); \ From 7d8066f1c35ca3635cb0058c816173adbc1e642a Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Sat, 8 Aug 2026 08:11:08 +0200 Subject: [PATCH 09/23] Fixed Zend/tests/fibers/get-return-after-bailout.phpt --- Zend/tests/fibers/get-return-after-bailout.phpt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Zend/tests/fibers/get-return-after-bailout.phpt b/Zend/tests/fibers/get-return-after-bailout.phpt index 79ab70c98baa..0d78ba2376c4 100644 --- a/Zend/tests/fibers/get-return-after-bailout.phpt +++ b/Zend/tests/fibers/get-return-after-bailout.phpt @@ -16,7 +16,8 @@ register_shutdown_function(static function (): void { }); $fiber = new Fiber(static function (): void { - str_repeat('X', PHP_INT_MAX); + $allocSize = PHP_INT_SIZE <= PHP_SYS_SIZE ? PHP_INT_MAX : 2 ** (PHP_SYS_SIZE * 8 - 1) - 1; + str_repeat('X', $allocSize); }); $fiber->start(); From 9ce4094d972b584737f1e40f07a7992a2566538d Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Mon, 7 Jul 2025 07:37:21 +0200 Subject: [PATCH 10/23] Fix ext/standard --- ext/standard/crypt_sha256.c | 2 +- ext/standard/crypt_sha512.c | 2 +- ext/standard/file.c | 49 +++++++--- ext/standard/head.c | 13 ++- ext/standard/head.h | 2 +- ext/standard/math.c | 6 +- ext/standard/metaphone.c | 12 ++- ext/standard/streamsfuncs.c | 54 ++++++++--- ext/standard/string.c | 97 ++++++++++++------- .../tests/file/fgetcsv_length_sizet.phpt | 21 ++++ .../tests/file/fgets_length_sizet.phpt | 61 ++++++++++++ .../file/file_get_contents_length_sizet.phpt | 19 ++++ .../tests/file/fread_length_sizet.phpt | 61 ++++++++++++ .../tests/file/ftruncate_error_sizet.phpt | 51 ++++++++++ ext/standard/tests/file/fwrite.phpt | 2 +- ext/standard/tests/file/umask_variation1.phpt | 2 +- ext/standard/tests/file/umask_variation2.phpt | 2 +- .../network/setcookie_max_age_64bit.phpt | 36 +++++++ ext/standard/tests/streams/bug61115-1.phpt | 2 +- ...stream_copy_to_stream_maxlength_sizet.phpt | 25 +++++ .../stream_get_contents_maxlength_sizet.phpt | 23 +++++ .../streams/stream_get_line_length_sizet.phpt | 23 +++++ .../streams/stream_set_read_buffer_sizet.phpt | 21 ++++ .../stream_set_write_buffer_sizet.phpt | 21 ++++ .../stream_socket_recvfrom_length_sizet.phpt | 55 +++++++++++ ext/standard/tests/strings/bug72146.phpt | 4 +- .../tests/strings/chunk_split_variation8.phpt | 7 +- ext/standard/tests/strings/gh15613.phpt | 2 +- .../strings/metaphone_max_phonemes_sizet.phpt | 57 +++++++++++ .../sprintf_rope_optimization_004.phpt | 16 +-- .../tests/strings/str_pad_variation1.phpt | 11 ++- .../tests/strings/str_pad_variation2.phpt | 2 +- .../tests/strings/str_pad_variation5.phpt | 56 +++++++++-- .../tests/strings/str_repeat_sizet.phpt | 47 +++++++++ .../tests/strings/str_split_variation6.phpt | 12 +++ ext/standard/tests/strings/stripos_error.phpt | 14 +++ ext/standard/tests/strings/strpos_error.phpt | 45 +++++++++ .../tests/strings/strripos_error.phpt | 45 +++++++++ ext/standard/tests/strings/strrpos_error.phpt | 45 +++++++++ ext/standard/tests/strings/substr.phpt | 18 ++++ .../tests/strings/substr_compare.phpt | 19 +++- .../tests/strings/substr_count_error.phpt | 34 +++++++ .../substr_replace_large_offset_length.phpt | 39 ++++++++ main/streams/memory.c | 2 + 44 files changed, 1030 insertions(+), 107 deletions(-) create mode 100644 ext/standard/tests/file/fgetcsv_length_sizet.phpt create mode 100644 ext/standard/tests/file/fgets_length_sizet.phpt create mode 100644 ext/standard/tests/file/file_get_contents_length_sizet.phpt create mode 100644 ext/standard/tests/file/fread_length_sizet.phpt create mode 100644 ext/standard/tests/file/ftruncate_error_sizet.phpt create mode 100644 ext/standard/tests/network/setcookie_max_age_64bit.phpt create mode 100644 ext/standard/tests/streams/stream_copy_to_stream_maxlength_sizet.phpt create mode 100644 ext/standard/tests/streams/stream_get_contents_maxlength_sizet.phpt create mode 100644 ext/standard/tests/streams/stream_get_line_length_sizet.phpt create mode 100644 ext/standard/tests/streams/stream_set_read_buffer_sizet.phpt create mode 100644 ext/standard/tests/streams/stream_set_write_buffer_sizet.phpt create mode 100644 ext/standard/tests/streams/stream_socket_recvfrom_length_sizet.phpt create mode 100644 ext/standard/tests/strings/metaphone_max_phonemes_sizet.phpt create mode 100644 ext/standard/tests/strings/str_repeat_sizet.phpt create mode 100644 ext/standard/tests/strings/strpos_error.phpt create mode 100644 ext/standard/tests/strings/strripos_error.phpt create mode 100644 ext/standard/tests/strings/strrpos_error.phpt create mode 100644 ext/standard/tests/strings/substr_replace_large_offset_length.phpt diff --git a/ext/standard/crypt_sha256.c b/ext/standard/crypt_sha256.c index 3f3d9cdeb03c..d2488b185397 100644 --- a/ext/standard/crypt_sha256.c +++ b/ext/standard/crypt_sha256.c @@ -510,7 +510,7 @@ char * php_sha256_crypt_r(const char *key, const char *salt, char *buffer, int b if (rounds_custom) { #ifdef PHP_WIN32 - int n = _snprintf(cp, MAX(0, buflen), "%s" ZEND_ULONG_FMT "$", sha256_rounds_prefix, rounds); + int n = _snprintf(cp, MAX(0, buflen), "%s%zu$", sha256_rounds_prefix, rounds); #else int n = snprintf(cp, MAX(0, buflen), "%s%zu$", sha256_rounds_prefix, rounds); #endif diff --git a/ext/standard/crypt_sha512.c b/ext/standard/crypt_sha512.c index 4a308e2f9af2..04f8448ff6d5 100644 --- a/ext/standard/crypt_sha512.c +++ b/ext/standard/crypt_sha512.c @@ -549,7 +549,7 @@ php_sha512_crypt_r(const char *key, const char *salt, char *buffer, int buflen) if (rounds_custom) { #ifdef PHP_WIN32 - int n = _snprintf(cp, MAX(0, buflen), "%s" ZEND_ULONG_FMT "$", sha512_rounds_prefix, rounds); + int n = _snprintf(cp, MAX(0, buflen), "%s%zu$", sha512_rounds_prefix, rounds); #else int n = snprintf(cp, MAX(0, buflen), "%s%zu$", sha512_rounds_prefix, rounds); #endif diff --git a/ext/standard/file.c b/ext/standard/file.c index b52e5ba9525f..e734c7d98a20 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -380,7 +380,8 @@ PHP_FUNCTION(file_get_contents) bool use_include_path = 0; php_stream *stream; zend_long offset = 0; - zend_long maxlen; + zend_long maxlen_zl; + size_t maxlen; bool maxlen_is_null = 1; zval *zcontext = NULL; php_stream_context *context = NULL; @@ -393,14 +394,19 @@ PHP_FUNCTION(file_get_contents) Z_PARAM_BOOL(use_include_path) Z_PARAM_RESOURCE_OR_NULL(zcontext) Z_PARAM_LONG(offset) - Z_PARAM_LONG_OR_NULL(maxlen, maxlen_is_null) + Z_PARAM_LONG_OR_NULL(maxlen_zl, maxlen_is_null) ZEND_PARSE_PARAMETERS_END(); if (maxlen_is_null) { - maxlen = (ssize_t) PHP_STREAM_COPY_ALL; - } else if (maxlen < 0) { + maxlen = PHP_STREAM_COPY_ALL; + } else if (UNEXPECTED(maxlen_zl < 0)) { zend_argument_value_error(5, "must be greater than or equal to 0"); RETURN_THROWS(); + } else if (ZEND_LONG_SIZE_T_OVFL(maxlen_zl)) { + zend_argument_value_error(5, "must be less than or equal to %zu", SIZE_MAX); + RETURN_THROWS(); + } else { + maxlen = (size_t) maxlen_zl; } php_stream_error_operation_begin(); @@ -504,9 +510,9 @@ PHP_FUNCTION(file_put_contents) if (php_stream_copy_to_stream_ex(srcstream, stream, PHP_STREAM_COPY_ALL, &len) != SUCCESS) { numbytes = -1; } else { - if (len > ZEND_LONG_MAX) { + if (ZEND_SIZE_T_ZEND_LONG_OVFL(len)) { php_error_docref(NULL, E_WARNING, "content truncated from %zu to " ZEND_LONG_FMT " bytes", len, ZEND_LONG_MAX); - len = ZEND_LONG_MAX; + len = (size_t) ZEND_LONG_MAX; } numbytes = len; } @@ -907,13 +913,16 @@ PHPAPI PHP_FUNCTION(fgets) RETVAL_STRINGL(buf, line_len); efree(buf); } else { - if (len <= 0) { + if (UNEXPECTED(len <= 0)) { zend_argument_value_error(2, "must be greater than 0"); RETURN_THROWS(); + } else if (ZEND_LONG_ZSTR_LEN_OVFL(len)) { + zend_argument_value_error(2, "must be less than or equal to %zu", ZSTR_MAX_LEN); + RETURN_THROWS(); } - str = zend_string_alloc(len, 0); - buf = php_stream_get_line(stream, ZSTR_VAL(str), len, &line_len); + str = zend_string_alloc((size_t) len, 0); + buf = php_stream_get_line(stream, ZSTR_VAL(str), (size_t) len, &line_len); php_stream_error_operation_end_for_stream(stream); if (buf == NULL) { zend_string_efree(str); @@ -1018,7 +1027,11 @@ PHPAPI PHP_FUNCTION(fwrite) } else if (maxlen <= 0) { num_bytes = 0; } else { +#if SIZEOF_SIZE_T >= SIZEOF_ZEND_LONG num_bytes = MIN((size_t) maxlen, inputlen); +#else + num_bytes = MIN(maxlen, (zend_long) inputlen); +#endif } if (!num_bytes) { @@ -1365,9 +1378,12 @@ PHP_FUNCTION(ftruncate) Z_PARAM_LONG(size) ZEND_PARSE_PARAMETERS_END(); - if (size < 0) { + if (UNEXPECTED(size < 0)) { zend_argument_value_error(2, "must be greater than or equal to 0"); RETURN_THROWS(); + } else if (ZEND_LONG_SIZE_T_OVFL(size)) { + zend_argument_value_error(2, "must be less than or equal to %zu", SIZE_MAX); + RETURN_THROWS(); } php_stream_error_operation_begin(); @@ -1613,9 +1629,12 @@ PHPAPI PHP_FUNCTION(fread) Z_PARAM_LONG(len) ZEND_PARSE_PARAMETERS_END(); - if (len <= 0) { + if (UNEXPECTED(len <= 0)) { zend_argument_value_error(2, "must be greater than 0"); RETURN_THROWS(); + } else if (ZEND_LONG_ZSTR_LEN_OVFL(len)) { + zend_argument_value_error(2, "must be less than or equal to %zu", ZSTR_MAX_LEN); + RETURN_THROWS(); } php_stream_error_operation_begin(); @@ -1869,9 +1888,15 @@ PHP_FUNCTION(fgetcsv) if (len_is_null || len == 0) { len = -1; - } else if (len < 0 || len > (ZEND_LONG_MAX - 1)) { +#if SIZEOF_SIZE_T >= SIZEOF_ZEND_LONG + } else if (UNEXPECTED(len < 0 || len > (ZEND_LONG_MAX - 1))) { zend_argument_value_error(2, "must be between 0 and " ZEND_LONG_FMT, (ZEND_LONG_MAX - 1)); RETURN_THROWS(); +#else + } else if (UNEXPECTED(len < 0 || len > (SIZE_MAX - 1))) { + zend_argument_value_error(2, "must be between 0 and %zu", (SIZE_MAX - 1)); + RETURN_THROWS(); +#endif } php_stream_error_operation_begin(); diff --git a/ext/standard/head.c b/ext/standard/head.c index 34a22327b5e7..9e43381b89af 100644 --- a/ext/standard/head.c +++ b/ext/standard/head.c @@ -82,7 +82,7 @@ PHPAPI bool php_is_valid_samesite_value(zend_string *value) } #define ILLEGAL_COOKIE_CHARACTER "\",\", \";\", \" \", \"\\t\", \"\\r\", \"\\n\", \"\\013\", or \"\\014\"" -PHPAPI zend_result php_setcookie(zend_string *name, zend_string *value, time_t expires, +PHPAPI zend_result php_setcookie(zend_string *name, zend_string *value, zend_long expires, zend_string *path, zend_string *domain, bool secure, bool httponly, zend_string *samesite, bool partitioned, bool url_encode) { @@ -115,7 +115,8 @@ PHPAPI zend_result php_setcookie(zend_string *name, zend_string *value, time_t e get_active_function_name()); return FAILURE; } -#ifdef ZEND_ENABLE_ZVAL_LONG64 + +#if SIZEOF_ZEND_LONG >= 8 if (expires >= 253402300800) { zend_value_error("%s(): \"expires\" option cannot have a year greater than 9999", get_active_function_name()); @@ -156,7 +157,7 @@ PHPAPI zend_result php_setcookie(zend_string *name, zend_string *value, time_t e } if (expires > 0) { - double diff; + zend_long diff; smart_str_appends(&buf, COOKIE_EXPIRES); dt = php_format_date("D, d M Y H:i:s \\G\\M\\T", sizeof("D, d M Y H:i:s \\G\\M\\T")-1, expires, 0); @@ -164,13 +165,15 @@ PHPAPI zend_result php_setcookie(zend_string *name, zend_string *value, time_t e smart_str_append(&buf, dt); zend_string_free(dt); - diff = difftime(expires, php_time()); + /* Not difftime(): its arguments are time_t, which is narrower than + * zend_long where ZEND_INT64 is enabled on a 32bit platform. */ + diff = expires - (zend_long) php_time(); if (diff < 0) { diff = 0; } smart_str_appends(&buf, COOKIE_MAX_AGE); - smart_str_append_long(&buf, (zend_long) diff); + smart_str_append_long(&buf, diff); } } diff --git a/ext/standard/head.h b/ext/standard/head.h index 8b91371a46e2..95b43342032f 100644 --- a/ext/standard/head.h +++ b/ext/standard/head.h @@ -29,7 +29,7 @@ PHPAPI bool php_is_valid_samesite_value(zend_string *value); extern PHP_RINIT_FUNCTION(head); PHPAPI bool php_header(void); -PHPAPI zend_result php_setcookie(zend_string *name, zend_string *value, time_t expires, +PHPAPI zend_result php_setcookie(zend_string *name, zend_string *value, zend_long expires, zend_string *path, zend_string *domain, bool secure, bool httponly, zend_string *samesite, bool partitioned, bool url_encode); diff --git a/ext/standard/math.c b/ext/standard/math.c index 20286870c7c9..4fda7f2785ad 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -1283,8 +1283,8 @@ PHPAPI zend_string *_php_math_number_format_long(zend_long num, zend_long dec, c 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, #if SIZEOF_ZEND_LONG == 8 - 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000, - 1000000000000000, 10000000000000000, 100000000000000000, 1000000000000000000, 10000000000000000000ul + Z_UL(10000000000), Z_UL(100000000000), Z_UL(1000000000000), Z_UL(10000000000000), Z_UL(100000000000000), + Z_UL(1000000000000000), Z_UL(10000000000000000), Z_UL(100000000000000000), Z_UL(1000000000000000000), Z_UL(10000000000000000000) #elif SIZEOF_ZEND_LONG > 8 # error "Unknown SIZEOF_ZEND_LONG" #endif @@ -1314,7 +1314,7 @@ PHPAPI zend_string *_php_math_number_format_long(zend_long num, zend_long dec, c // rounding the number if (dec < 0) { // Check rounding to more negative places than possible - if (dec < -(sizeof(powers) / sizeof(powers[0]) - 1)) { + if (UNEXPECTED(dec < -(zend_long)(sizeof(powers) / sizeof(powers[0]) - 1))) { tmpnum = 0; } else { power = powers[-dec]; diff --git a/ext/standard/metaphone.c b/ext/standard/metaphone.c index 8f33057de5ac..0221343c6595 100644 --- a/ext/standard/metaphone.c +++ b/ext/standard/metaphone.c @@ -33,10 +33,20 @@ PHP_FUNCTION(metaphone) Z_PARAM_LONG(phones) ZEND_PARSE_PARAMETERS_END(); - if (phones < 0) { + if (UNEXPECTED(phones < 0)) { zend_argument_value_error(2, "must be greater than or equal to 0"); RETURN_THROWS(); } +#if SIZEOF_SIZE_T < SIZEOF_ZEND_LONG + /* metaphone() below allocates max_phonemes + 1 bytes, so the usable limit + * is one below ZSTR_MAX_LEN. Guarded inline rather than through + * ZEND_LONG_ZSTR_LEN_OVFL() because (zend_long) (ZSTR_MAX_LEN - 1) is + * negative where size_t is at least as wide as zend_long. */ + if (UNEXPECTED(phones > (zend_long) (ZSTR_MAX_LEN - 1))) { + zend_argument_value_error(2, "must be less than or equal to %zu", ZSTR_MAX_LEN - 1); + RETURN_THROWS(); + } +#endif metaphone((unsigned char *)ZSTR_VAL(str), ZSTR_LEN(str), phones, &result, 1); RETVAL_STR(result); diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index d544e8bf5f0f..269a5715be95 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -425,15 +425,18 @@ PHP_FUNCTION(stream_socket_recvfrom) ZEND_TRY_ASSIGN_REF_NULL(zremote); } - if (to_read <= 0) { + if (UNEXPECTED(to_read <= 0)) { zend_argument_value_error(2, "must be greater than 0"); RETURN_THROWS(); + } else if (ZEND_LONG_ZSTR_LEN_OVFL(to_read)) { + zend_argument_value_error(2, "must be less than or equal to %zu", ZSTR_MAX_LEN); + RETURN_THROWS(); } - read_buf = zend_string_alloc(to_read, 0); + read_buf = zend_string_alloc((size_t) to_read, 0); php_stream_error_operation_begin(); - recvd = php_stream_xport_recvfrom(stream, ZSTR_VAL(read_buf), to_read, (int)flags, NULL, NULL, + recvd = php_stream_xport_recvfrom(stream, ZSTR_VAL(read_buf), (size_t) to_read, (int)flags, NULL, NULL, zremote ? &remote_addr : NULL); php_stream_error_operation_end_for_stream(stream); @@ -455,22 +458,28 @@ PHP_FUNCTION(stream_socket_recvfrom) PHP_FUNCTION(stream_get_contents) { php_stream *stream; - zend_long maxlen, desiredpos = -1L; + zend_long maxlen_zl, desiredpos = -1L; + size_t maxlen; bool maxlen_is_null = 1; zend_string *contents; ZEND_PARSE_PARAMETERS_START(1, 3) PHP_Z_PARAM_STREAM(stream) Z_PARAM_OPTIONAL - Z_PARAM_LONG_OR_NULL(maxlen, maxlen_is_null) + Z_PARAM_LONG_OR_NULL(maxlen_zl, maxlen_is_null) Z_PARAM_LONG(desiredpos) ZEND_PARSE_PARAMETERS_END(); - if (maxlen_is_null) { - maxlen = (ssize_t) PHP_STREAM_COPY_ALL; - } else if (maxlen < 0 && maxlen != (ssize_t)PHP_STREAM_COPY_ALL) { + if (maxlen_is_null || maxlen_zl == -1) { + maxlen = PHP_STREAM_COPY_ALL; + } else if (UNEXPECTED(maxlen_zl < 0)) { zend_argument_value_error(2, "must be greater than or equal to -1"); RETURN_THROWS(); + } else if (ZEND_LONG_SIZE_T_OVFL(maxlen_zl)) { + zend_argument_value_error(2, "must be less than or equal to %zu", SIZE_MAX); + RETURN_THROWS(); + } else { + maxlen = (size_t) maxlen_zl; } php_stream_error_operation_begin(); @@ -496,7 +505,7 @@ PHP_FUNCTION(stream_get_contents) } } - if ((contents = php_stream_copy_to_mem(stream, maxlen, 0))) { + if ((contents = php_stream_copy_to_mem(stream, (size_t) maxlen, 0))) { RETVAL_STR(contents); } else { RETVAL_EMPTY_STRING(); @@ -526,6 +535,9 @@ PHP_FUNCTION(stream_copy_to_stream) if (maxlen_is_null) { maxlen = PHP_STREAM_COPY_ALL; + } else if (ZEND_LONG_SIZE_T_OVFL(maxlen)) { + zend_argument_value_error(3, "must be less than or equal to %zu", SIZE_MAX); + RETURN_THROWS(); } php_stream_error_operation_begin(); @@ -537,7 +549,7 @@ PHP_FUNCTION(stream_copy_to_stream) RETURN_FALSE; } - if (php_stream_copy_to_stream_ex(src, dest, maxlen, &len) != SUCCESS) { + if (php_stream_copy_to_stream_ex(src, dest, (size_t) maxlen, &len) != SUCCESS) { RETVAL_FALSE; } else { RETVAL_LONG(len); @@ -1420,16 +1432,20 @@ PHP_FUNCTION(stream_get_line) Z_PARAM_STRING(str, str_len) ZEND_PARSE_PARAMETERS_END(); - if (max_length < 0) { + if (UNEXPECTED(max_length < 0)) { zend_argument_value_error(2, "must be greater than or equal to 0"); RETURN_THROWS(); + } else if (ZEND_LONG_SIZE_T_OVFL(max_length)) { + zend_argument_value_error(2, "must be less than or equal to %zu", SIZE_MAX); + RETURN_THROWS(); } + if (!max_length) { max_length = PHP_SOCK_CHUNK_SIZE; } php_stream_error_operation_begin(); - if ((buf = php_stream_get_record(stream, max_length, str, str_len))) { + if ((buf = php_stream_get_record(stream, (size_t) max_length, str, str_len))) { RETVAL_STR(buf); } else { RETVAL_FALSE; @@ -1513,7 +1529,12 @@ PHP_FUNCTION(stream_set_write_buffer) Z_PARAM_LONG(arg2) ZEND_PARSE_PARAMETERS_END(); - buff = arg2; + if (ZEND_LONG_SIZE_T_OVFL(arg2)) { + zend_argument_value_error(2, "must be less than or equal to %zu", SIZE_MAX); + RETURN_THROWS(); + } + + buff = (size_t) arg2; php_stream_error_operation_begin(); /* if buff is 0 then set to non-buffered */ @@ -1574,7 +1595,12 @@ PHP_FUNCTION(stream_set_read_buffer) Z_PARAM_LONG(arg2) ZEND_PARSE_PARAMETERS_END(); - buff = arg2; + if (ZEND_LONG_SIZE_T_OVFL(arg2)) { + zend_argument_value_error(2, "must be less than or equal to %zu", SIZE_MAX); + RETURN_THROWS(); + } + + buff = (size_t) arg2; php_stream_error_operation_begin(); /* if buff is 0 then set to non-buffered */ diff --git a/ext/standard/string.c b/ext/standard/string.c index 577d4267ed2f..005496d39494 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -188,7 +188,7 @@ static void php_spn_common_handler(INTERNAL_FUNCTION_PARAMETERS, bool is_strspn) if (start < 0) { start = 0; } - } else if ((size_t) start > remain_len) { + } else if (ZEND_LONG_GT_SIZE_T(start, remain_len)) { start = remain_len; } @@ -199,7 +199,7 @@ static void php_spn_common_handler(INTERNAL_FUNCTION_PARAMETERS, bool is_strspn) if (len < 0) { len = 0; } - } else if ((size_t) len > remain_len) { + } else if (ZEND_LONG_GT_SIZE_T(len, remain_len)) { len = remain_len; } } else { @@ -1973,7 +1973,7 @@ static zend_always_inline void _zend_strpos(zval *return_value, zend_string *hay if (offset < 0) { offset += (zend_long)ZSTR_LEN(haystack); } - if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) { + if (offset < 0 || ZEND_LONG_GT_SIZE_T(offset, ZSTR_LEN(haystack))) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } @@ -2054,7 +2054,7 @@ PHP_FUNCTION(stripos) if (offset < 0) { offset += (zend_long)ZSTR_LEN(haystack); } - if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) { + if (offset < 0 || ZEND_LONG_GT_SIZE_T(offset, ZSTR_LEN(haystack))) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } @@ -2085,14 +2085,14 @@ PHP_FUNCTION(strrpos) ZEND_PARSE_PARAMETERS_END(); if (offset >= 0) { - if ((size_t)offset > ZSTR_LEN(haystack)) { + if (ZEND_LONG_GT_SIZE_T(offset, ZSTR_LEN(haystack))) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } p = ZSTR_VAL(haystack) + (size_t)offset; e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack); } else { - if (offset < -ZEND_LONG_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) { + if (offset < -ZEND_LONG_MAX || ZEND_LONG_GT_SIZE_T(-offset, ZSTR_LEN(haystack))) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } @@ -2135,7 +2135,7 @@ PHP_FUNCTION(strripos) Can also avoid tolower emallocs */ char lowered; if (offset >= 0) { - if ((size_t)offset > ZSTR_LEN(haystack)) { + if (ZEND_LONG_GT_SIZE_T(offset, ZSTR_LEN(haystack))) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } @@ -2143,7 +2143,7 @@ PHP_FUNCTION(strripos) e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - 1; } else { p = ZSTR_VAL(haystack); - if (offset < -ZEND_LONG_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) { + if (offset < -ZEND_LONG_MAX || ZEND_LONG_GT_SIZE_T(-offset, ZSTR_LEN(haystack))) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } @@ -2161,7 +2161,7 @@ PHP_FUNCTION(strripos) haystack_dup = zend_string_tolower(haystack); if (offset >= 0) { - if ((size_t)offset > ZSTR_LEN(haystack)) { + if (ZEND_LONG_GT_SIZE_T(offset, ZSTR_LEN(haystack))) { zend_string_release_ex(haystack_dup, 0); zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); @@ -2169,7 +2169,7 @@ PHP_FUNCTION(strripos) p = ZSTR_VAL(haystack_dup) + offset; e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack); } else { - if (offset < -ZEND_LONG_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) { + if (offset < -ZEND_LONG_MAX || ZEND_LONG_GT_SIZE_T(-offset, ZSTR_LEN(haystack))) { zend_string_release_ex(haystack_dup, 0); zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); @@ -2274,9 +2274,11 @@ PHP_FUNCTION(chunk_split) Z_PARAM_STRING(end, endlen) ZEND_PARSE_PARAMETERS_END(); - if (chunklen <= 0) { + if (UNEXPECTED(chunklen <= 0)) { zend_argument_value_error(2, "must be greater than 0"); RETURN_THROWS(); + } else if (ZEND_LONG_SIZE_T_OVFL(chunklen)) { + chunklen = (zend_long) SIZE_MAX; } if ((size_t)chunklen > ZSTR_LEN(str)) { @@ -2304,12 +2306,11 @@ static inline void _zend_substr(zval *return_value, zend_string *str, zend_long /* if "from" position is negative, count start position from the end * of the string */ - if (-(size_t)f > ZSTR_LEN(str)) { + f = (zend_long)ZSTR_LEN(str) + f; + if (f < 0) { f = 0; - } else { - f = (zend_long)ZSTR_LEN(str) + f; } - } else if ((size_t)f > ZSTR_LEN(str)) { + } else if (ZEND_LONG_GT_SIZE_T(f, ZSTR_LEN(str))) { RETURN_EMPTY_STRING(); } @@ -2318,13 +2319,18 @@ static inline void _zend_substr(zval *return_value, zend_string *str, zend_long /* if "length" position is negative, set it to the length * needed to stop that many chars from the end of the string */ - if (-(size_t)l > ZSTR_LEN(str) - (size_t)f) { + l = (zend_long)ZSTR_LEN(str) - f + l; + if (l < 0) { l = 0; - } else { - l = (zend_long)ZSTR_LEN(str) - f + l; } - } else if ((size_t)l > ZSTR_LEN(str) - (size_t)f) { - l = (zend_long)ZSTR_LEN(str) - f; + } else { + if (ZEND_LONG_SIZE_T_OVFL(l)) { + l = SIZE_MAX; + } + + if ((size_t)l > ZSTR_LEN(str) - (size_t)f) { + l = (zend_long)ZSTR_LEN(str) - f; + } } } else { l = (zend_long)ZSTR_LEN(str) - f; @@ -2439,7 +2445,7 @@ PHP_FUNCTION(substr_replace) if (f < 0) { f = 0; } - } else if ((size_t)f > ZSTR_LEN(str)) { + } else if (ZEND_LONG_GT_SIZE_T(f, ZSTR_LEN(str))) { f = ZSTR_LEN(str); } /* if "length" position is negative, set it to the length @@ -2452,7 +2458,7 @@ PHP_FUNCTION(substr_replace) } } - if ((size_t)l > ZSTR_LEN(str)) { + if (ZEND_LONG_GT_SIZE_T(l, ZSTR_LEN(str))) { l = ZSTR_LEN(str); } @@ -2545,7 +2551,7 @@ PHP_FUNCTION(substr_replace) if (f < 0) { f = 0; } - } else if (f > (zend_long)ZSTR_LEN(orig_str)) { + } else if (ZEND_LONG_GT_SIZE_T(f, ZSTR_LEN(orig_str))) { f = ZSTR_LEN(orig_str); } from_idx++; @@ -2559,7 +2565,7 @@ PHP_FUNCTION(substr_replace) if (f < 0) { f = 0; } - } else if (f > (zend_long)ZSTR_LEN(orig_str)) { + } else if (ZEND_LONG_GT_SIZE_T(f, ZSTR_LEN(orig_str))) { f = ZSTR_LEN(orig_str); } } @@ -2603,7 +2609,7 @@ PHP_FUNCTION(substr_replace) ZEND_ASSERT(0 <= f && f <= ZEND_LONG_MAX); ZEND_ASSERT(0 <= l && l <= ZEND_LONG_MAX); - if (((size_t) f + l) > ZSTR_LEN(orig_str)) { + if (ZEND_ULONG_GT_SIZE_T((zend_ulong) f + (zend_ulong) l, ZSTR_LEN(orig_str))) { l = ZSTR_LEN(orig_str) - f; } @@ -5627,6 +5633,9 @@ PHP_FUNCTION(str_repeat) if (mult < 0) { zend_argument_value_error(2, "must be greater than or equal to 0"); RETURN_THROWS(); + } else if (ZEND_LONG_SIZE_T_OVFL(mult)) { + zend_argument_value_error(2, "must be less than or equal to %zu", SIZE_MAX); + RETURN_THROWS(); } /* Don't waste our time if it's empty */ @@ -5635,13 +5644,13 @@ PHP_FUNCTION(str_repeat) RETURN_EMPTY_STRING(); /* Initialize the result string */ - result = zend_string_safe_alloc(ZSTR_LEN(input_str), mult, 0, 0); - result_len = ZSTR_LEN(input_str) * mult; + result = zend_string_safe_alloc(ZSTR_LEN(input_str), (size_t) mult, 0, 0); + result_len = ZSTR_LEN(input_str) * (size_t) mult; ZSTR_COPY_CONCAT_PROPERTIES(result, input_str); /* Heavy optimization for situations where input string is 1 byte long */ if (ZSTR_LEN(input_str) == 1) { - memset(ZSTR_VAL(result), *ZSTR_VAL(input_str), mult); + memset(ZSTR_VAL(result), *ZSTR_VAL(input_str), (size_t) mult); } else { const char *s, *ee; char *e; @@ -5847,7 +5856,7 @@ PHP_FUNCTION(substr_count) if (offset < 0) { offset += (zend_long)haystack_len; } - if ((offset < 0) || ((size_t)offset > haystack_len)) { + if ((offset < 0) || ZEND_LONG_GT_SIZE_T(offset, haystack_len)) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } @@ -5859,7 +5868,7 @@ PHP_FUNCTION(substr_count) if (length < 0) { length += haystack_len; } - if (length < 0 || ((size_t)length > haystack_len)) { + if (length < 0 || ZEND_LONG_GT_SIZE_T(length, haystack_len)) { zend_argument_value_error(4, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } @@ -5928,6 +5937,11 @@ PHP_FUNCTION(str_pad) Z_PARAM_LONG(pad_type_val) ZEND_PARSE_PARAMETERS_END(); + if (ZEND_LONG_ZSTR_LEN_OVFL(pad_length)) { + zend_argument_value_error(2, "must be less than or equal to %zu", ZSTR_MAX_LEN); + RETURN_THROWS(); + } + /* If resulting string turns out to be shorter than input string, we simply copy the input and return. */ if (pad_length < 0 || (size_t)pad_length <= ZSTR_LEN(input)) { @@ -6277,7 +6291,7 @@ PHP_FUNCTION(str_split) RETURN_THROWS(); } - if ((size_t)split_length >= ZSTR_LEN(str)) { + if (ZEND_LONG_GTE_SIZE_T(split_length, ZSTR_LEN(str))) { if (0 == ZSTR_LEN(str)) { RETURN_EMPTY_ARRAY(); } @@ -6368,21 +6382,30 @@ PHP_FUNCTION(substr_compare) } if (offset < 0) { - offset = ZSTR_LEN(s1) + offset; - offset = (offset < 0) ? 0 : offset; + if (offset < -((zend_long) ZSTR_LEN(s1))) { + offset = 0; + } else { + offset = ZSTR_LEN(s1) + offset; + } } - if ((size_t)offset > ZSTR_LEN(s1)) { + if (ZEND_LONG_GT_SIZE_T(offset, ZSTR_LEN(s1))) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } - cmp_len = len ? (size_t)len : MAX(ZSTR_LEN(s2), (ZSTR_LEN(s1) - offset)); + if (!len) { + cmp_len = MAX(ZSTR_LEN(s2), (ZSTR_LEN(s1) - (size_t) offset)); + } else if (ZEND_LONG_SIZE_T_OVFL(len)) { + cmp_len = SIZE_MAX; + } else { + cmp_len = (size_t) len; + } if (!cs) { - RETURN_LONG(zend_binary_strncmp(ZSTR_VAL(s1) + offset, (ZSTR_LEN(s1) - offset), ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len)); + RETURN_LONG(zend_binary_strncmp(ZSTR_VAL(s1) + (size_t) offset, ZSTR_LEN(s1) - (size_t) offset, ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len)); } else { - RETURN_LONG(zend_binary_strncasecmp_l(ZSTR_VAL(s1) + offset, (ZSTR_LEN(s1) - offset), ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len)); + RETURN_LONG(zend_binary_strncasecmp_l(ZSTR_VAL(s1) + (size_t) offset, ZSTR_LEN(s1) - (size_t) offset, ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len)); } } /* }}} */ diff --git a/ext/standard/tests/file/fgetcsv_length_sizet.phpt b/ext/standard/tests/file/fgetcsv_length_sizet.phpt new file mode 100644 index 000000000000..3ee97763f0e9 --- /dev/null +++ b/ext/standard/tests/file/fgetcsv_length_sizet.phpt @@ -0,0 +1,21 @@ +--TEST-- +fgetcsv() $length overflow on narrow size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; +} +fclose($f); +?> +--EXPECTF-- +fgetcsv(): Argument #2 ($length) must be between 0 and %d diff --git a/ext/standard/tests/file/fgets_length_sizet.phpt b/ext/standard/tests/file/fgets_length_sizet.phpt new file mode 100644 index 000000000000..de024352e262 --- /dev/null +++ b/ext/standard/tests/file/fgets_length_sizet.phpt @@ -0,0 +1,61 @@ +--TEST-- +fgets() $length overflow on narrow size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; +} + +/* fgets() allocates its line buffer with zend_string_alloc($length), which + * adds the header and the terminating NUL to the length without an overflow + * check, so anything above PHP_STRING_MAX_LENGTH wraps to a tiny allocation + * that php_stream_get_line() then overruns. The whole window from the limit + * up to SIZE_MAX has to be refused, not just the values that exceed SIZE_MAX + * itself. */ +$sizeMax = 2 ** (PHP_SYS_SIZE * 8) - 1; + +$lengths = [ + 'STR_MAX+1' => PHP_STRING_MAX_LENGTH + 1, + 'STR_MAX+2' => PHP_STRING_MAX_LENGTH + 2, + 'STR_MAX+3' => PHP_STRING_MAX_LENGTH + 3, + 'SIZE_MAX-2' => $sizeMax - 2, + 'SIZE_MAX-1' => $sizeMax - 1, + 'SIZE_MAX' => $sizeMax, +]; + +foreach ($lengths as $label => $length) { + try { + fgets($f, $length); + echo "$label: unexpected success\n"; + } catch (ValueError $e) { + echo "$label: {$e->getMessage()}\n"; + } +} + +fclose($f); +?> +--CLEAN-- + +--EXPECTF-- +fgets(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+1: fgets(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+2: fgets(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+3: fgets(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX-2: fgets(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX-1: fgets(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX: fgets(): Argument #2 ($length) must be less than or equal to %d diff --git a/ext/standard/tests/file/file_get_contents_length_sizet.phpt b/ext/standard/tests/file/file_get_contents_length_sizet.phpt new file mode 100644 index 000000000000..064135883fe4 --- /dev/null +++ b/ext/standard/tests/file/file_get_contents_length_sizet.phpt @@ -0,0 +1,19 @@ +--TEST-- +file_get_contents() $length overflow on narrow size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECTF-- +file_get_contents(): Argument #5 ($length) must be less than or equal to %d diff --git a/ext/standard/tests/file/fread_length_sizet.phpt b/ext/standard/tests/file/fread_length_sizet.phpt new file mode 100644 index 000000000000..3289c4ff5029 --- /dev/null +++ b/ext/standard/tests/file/fread_length_sizet.phpt @@ -0,0 +1,61 @@ +--TEST-- +fread() $length overflow on narrow size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; +} + +/* fread() reaches zend_string_alloc($length) via php_stream_read_to_str(). + * zend_string_alloc() adds the header and the terminating NUL to the length + * without an overflow check, so anything above PHP_STRING_MAX_LENGTH wraps to + * a tiny allocation that the following read then overruns. The whole window + * from the limit up to SIZE_MAX has to be refused, not just the values that + * exceed SIZE_MAX itself. */ +$sizeMax = 2 ** (PHP_SYS_SIZE * 8) - 1; + +$lengths = [ + 'STR_MAX+1' => PHP_STRING_MAX_LENGTH + 1, + 'STR_MAX+2' => PHP_STRING_MAX_LENGTH + 2, + 'STR_MAX+3' => PHP_STRING_MAX_LENGTH + 3, + 'SIZE_MAX-2' => $sizeMax - 2, + 'SIZE_MAX-1' => $sizeMax - 1, + 'SIZE_MAX' => $sizeMax, +]; + +foreach ($lengths as $label => $length) { + try { + fread($f, $length); + echo "$label: unexpected success\n"; + } catch (ValueError $e) { + echo "$label: {$e->getMessage()}\n"; + } +} + +fclose($f); +?> +--CLEAN-- + +--EXPECTF-- +fread(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+1: fread(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+2: fread(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+3: fread(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX-2: fread(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX-1: fread(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX: fread(): Argument #2 ($length) must be less than or equal to %d diff --git a/ext/standard/tests/file/ftruncate_error_sizet.phpt b/ext/standard/tests/file/ftruncate_error_sizet.phpt new file mode 100644 index 000000000000..2cc252182f47 --- /dev/null +++ b/ext/standard/tests/file/ftruncate_error_sizet.phpt @@ -0,0 +1,51 @@ +--TEST-- +ftruncate() on memory streams rejects sizes that exceed ZSTR_MAX_LEN +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; + } + + try { + $f = fopen('php://memory', 'w+'); + var_dump(ftruncate($f, $size)); + } catch (ValueError $e) { + echo $e->getMessage(), "\n"; + } +} + +echo "done\n"; +?> +--EXPECTF-- +bool(false) +bool(false) +bool(false) +bool(false) +ftruncate(): Argument #2 ($size) must be less than or equal to %d +ftruncate(): Argument #2 ($size) must be less than or equal to %d +ftruncate(): Argument #2 ($size) must be less than or equal to %d +ftruncate(): Argument #2 ($size) must be less than or equal to %d +bool(false) +bool(false) + +Fatal error: Allowed memory size of %d bytes exhausted at %s (tried to allocate %d bytes) in %s on line %d diff --git a/ext/standard/tests/file/fwrite.phpt b/ext/standard/tests/file/fwrite.phpt index e29c6a3507de..7f284d8c21b6 100644 --- a/ext/standard/tests/file/fwrite.phpt +++ b/ext/standard/tests/file/fwrite.phpt @@ -14,7 +14,7 @@ var_dump(fwrite($fp, "data")); $fp = fopen($filename, "w"); var_dump(fwrite($fp, "data", -1)); -var_dump(fwrite($fp, "data", 100000)); +var_dump(fwrite($fp, "data", PHP_INT_MAX)); fclose($fp); var_dump(file_get_contents($filename)); diff --git a/ext/standard/tests/file/umask_variation1.phpt b/ext/standard/tests/file/umask_variation1.phpt index f5a5a26e5577..dbab7dd52fb7 100644 --- a/ext/standard/tests/file/umask_variation1.phpt +++ b/ext/standard/tests/file/umask_variation1.phpt @@ -8,7 +8,7 @@ if (substr(PHP_OS, 0, 3) == 'WIN') { ?> --FILE-- --FILE-- +--INI-- +date.timezone=UTC +--FILE-- + 0 ? 'ok' : "WRONG: $max_age", "\n"; + echo 'Max-Age correct: ', + abs($max_age - $expected) <= 2 ? 'ok' : "WRONG: $max_age, expected about $expected", "\n"; +} +?> +--EXPECT-- +expires attribute: ok +Max-Age positive: ok +Max-Age correct: ok diff --git a/ext/standard/tests/streams/bug61115-1.phpt b/ext/standard/tests/streams/bug61115-1.phpt index 892b0f80e579..d07b7a7f0756 100644 --- a/ext/standard/tests/streams/bug61115-1.phpt +++ b/ext/standard/tests/streams/bug61115-1.phpt @@ -11,7 +11,7 @@ if (getenv("USE_ZEND_ALLOC") === "0") { $fileResourceTemp = fopen('php://temp', 'wr'); stream_context_get_options($fileResourceTemp); -ftruncate($fileResourceTemp, PHP_INT_MAX); +ftruncate($fileResourceTemp, ((2 ** (PHP_SYS_SIZE * 8-2) - 1) << 1) + 1); ?> --EXPECTF-- Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d diff --git a/ext/standard/tests/streams/stream_copy_to_stream_maxlength_sizet.phpt b/ext/standard/tests/streams/stream_copy_to_stream_maxlength_sizet.phpt new file mode 100644 index 000000000000..05ad051453cf --- /dev/null +++ b/ext/standard/tests/streams/stream_copy_to_stream_maxlength_sizet.phpt @@ -0,0 +1,25 @@ +--TEST-- +stream_copy_to_stream() $maxLength overflow on narrow size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; +} +fclose($src); +fclose($dst); +?> +--EXPECTF-- +stream_copy_to_stream(): Argument #3 ($length) must be less than or equal to %d diff --git a/ext/standard/tests/streams/stream_get_contents_maxlength_sizet.phpt b/ext/standard/tests/streams/stream_get_contents_maxlength_sizet.phpt new file mode 100644 index 000000000000..2f5f0930a01e --- /dev/null +++ b/ext/standard/tests/streams/stream_get_contents_maxlength_sizet.phpt @@ -0,0 +1,23 @@ +--TEST-- +stream_get_contents() $maxLength overflow on narrow size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; +} +fclose($s); +?> +--EXPECTF-- +stream_get_contents(): Argument #2 ($length) must be less than or equal to %d diff --git a/ext/standard/tests/streams/stream_get_line_length_sizet.phpt b/ext/standard/tests/streams/stream_get_line_length_sizet.phpt new file mode 100644 index 000000000000..5547ec6289aa --- /dev/null +++ b/ext/standard/tests/streams/stream_get_line_length_sizet.phpt @@ -0,0 +1,23 @@ +--TEST-- +stream_get_line() $length overflow on narrow size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; +} +fclose($s); +?> +--EXPECTF-- +stream_get_line(): Argument #2 ($length) must be less than or equal to %d diff --git a/ext/standard/tests/streams/stream_set_read_buffer_sizet.phpt b/ext/standard/tests/streams/stream_set_read_buffer_sizet.phpt new file mode 100644 index 000000000000..ae18e9179cd4 --- /dev/null +++ b/ext/standard/tests/streams/stream_set_read_buffer_sizet.phpt @@ -0,0 +1,21 @@ +--TEST-- +stream_set_read_buffer() $size overflow on narrow size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; +} +fclose($s); +?> +--EXPECTF-- +stream_set_read_buffer(): Argument #2 ($size) must be less than or equal to %d diff --git a/ext/standard/tests/streams/stream_set_write_buffer_sizet.phpt b/ext/standard/tests/streams/stream_set_write_buffer_sizet.phpt new file mode 100644 index 000000000000..a5556f57778b --- /dev/null +++ b/ext/standard/tests/streams/stream_set_write_buffer_sizet.phpt @@ -0,0 +1,21 @@ +--TEST-- +stream_set_write_buffer() $size overflow on narrow size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; +} +fclose($s); +?> +--EXPECTF-- +stream_set_write_buffer(): Argument #2 ($size) must be less than or equal to %d diff --git a/ext/standard/tests/streams/stream_socket_recvfrom_length_sizet.phpt b/ext/standard/tests/streams/stream_socket_recvfrom_length_sizet.phpt new file mode 100644 index 000000000000..8fa520d9f1d9 --- /dev/null +++ b/ext/standard/tests/streams/stream_socket_recvfrom_length_sizet.phpt @@ -0,0 +1,55 @@ +--TEST-- +stream_socket_recvfrom() $length overflow on narrow size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; +} + +/* The receive buffer comes from zend_string_alloc($length), which adds the + * header and the terminating NUL to the length without an overflow check, so + * anything above PHP_STRING_MAX_LENGTH wraps to a tiny allocation that is then + * handed to the transport as if it were $length bytes wide. The whole window + * from the limit up to SIZE_MAX has to be refused, not just the values that + * exceed SIZE_MAX itself. */ +$sizeMax = 2 ** (PHP_SYS_SIZE * 8) - 1; + +$lengths = [ + 'STR_MAX+1' => PHP_STRING_MAX_LENGTH + 1, + 'STR_MAX+2' => PHP_STRING_MAX_LENGTH + 2, + 'STR_MAX+3' => PHP_STRING_MAX_LENGTH + 3, + 'SIZE_MAX-2' => $sizeMax - 2, + 'SIZE_MAX-1' => $sizeMax - 1, + 'SIZE_MAX' => $sizeMax, +]; + +foreach ($lengths as $label => $length) { + try { + stream_socket_recvfrom($sock, $length); + echo "$label: unexpected success\n"; + } catch (ValueError $e) { + echo "$label: {$e->getMessage()}\n"; + } +} + +fclose($sock); +?> +--EXPECTF-- +stream_socket_recvfrom(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+1: stream_socket_recvfrom(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+2: stream_socket_recvfrom(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+3: stream_socket_recvfrom(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX-2: stream_socket_recvfrom(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX-1: stream_socket_recvfrom(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX: stream_socket_recvfrom(): Argument #2 ($length) must be less than or equal to %d diff --git a/ext/standard/tests/strings/bug72146.phpt b/ext/standard/tests/strings/bug72146.phpt index 72b332570ff2..3802bc2122b9 100644 --- a/ext/standard/tests/strings/bug72146.phpt +++ b/ext/standard/tests/strings/bug72146.phpt @@ -1,8 +1,8 @@ --TEST-- Bug #72146 (Integer overflow on substr_replace) --FILE-- - --EXPECT-- array(1) { diff --git a/ext/standard/tests/strings/chunk_split_variation8.phpt b/ext/standard/tests/strings/chunk_split_variation8.phpt index b0c889494e40..9d07a6a843c6 100644 --- a/ext/standard/tests/strings/chunk_split_variation8.phpt +++ b/ext/standard/tests/strings/chunk_split_variation8.phpt @@ -33,7 +33,7 @@ $values = array ( PHP_INT_MAX, // max positive integer number PHP_INT_MAX * 3, // integer overflow -PHP_INT_MAX - 1, // min negative integer - + (2 ** (PHP_SYS_SIZE * 8 - 2) - 1 << 1) + 1, // SSIZE_MAX ); @@ -80,3 +80,8 @@ chunk_split():::" chunk_split(): Argument #2 ($length) must be of type int, float given -- Iteration 8 -- chunk_split(): Argument #2 ($length) must be greater than 0 +-- Iteration 9 -- +string(129) "This's heredoc string with and + white space char. +It has _speci@l ch@r$ 2222 !!!Now \k as escape char to test +chunk_split():::" diff --git a/ext/standard/tests/strings/gh15613.phpt b/ext/standard/tests/strings/gh15613.phpt index 44c41acfddd1..20ff1620ee81 100644 --- a/ext/standard/tests/strings/gh15613.phpt +++ b/ext/standard/tests/strings/gh15613.phpt @@ -3,7 +3,7 @@ GH-15613 overflow on hex strings repeater value --SKIPIF-- --INI-- memory_limit=-1 diff --git a/ext/standard/tests/strings/metaphone_max_phonemes_sizet.phpt b/ext/standard/tests/strings/metaphone_max_phonemes_sizet.phpt new file mode 100644 index 000000000000..846bd7b3cfa5 --- /dev/null +++ b/ext/standard/tests/strings/metaphone_max_phonemes_sizet.phpt @@ -0,0 +1,57 @@ +--TEST-- +metaphone() $maxPhonemes overflow on narrow size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- +getMessage(), "\n"; +} + +error_reporting(E_ALL & ~E_DEPRECATED); + +/* metaphone() allocates $max_phonemes + 1 bytes with zend_string_alloc(), + * which adds the header and the terminating NUL to that without an overflow + * check. Its own limit is therefore one below PHP_STRING_MAX_LENGTH, so the + * window of refused values starts at the constant itself rather than above + * it. Beyond the limit the allocation wraps to a tiny buffer whose ZSTR_LEN + * is huge and the phoneme loop writes past the end of it; a long word is used + * so that an unfixed build overruns far enough to fault instead of corrupting + * the heap silently. */ +$sizeMax = 2 ** (PHP_SYS_SIZE * 8) - 1; +$word = str_repeat('Thompson', 1000); + +$lengths = [ + 'STR_MAX' => PHP_STRING_MAX_LENGTH, + 'STR_MAX+1' => PHP_STRING_MAX_LENGTH + 1, + 'STR_MAX+2' => PHP_STRING_MAX_LENGTH + 2, + 'SIZE_MAX-2' => $sizeMax - 2, + 'SIZE_MAX-1' => $sizeMax - 1, + 'SIZE_MAX' => $sizeMax, +]; + +foreach ($lengths as $label => $length) { + try { + metaphone($word, $length); + echo "$label: unexpected success\n"; + } catch (ValueError $e) { + echo "$label: {$e->getMessage()}\n"; + } +} +?> +--EXPECTF-- +Deprecated: Function metaphone() is deprecated since 8.6, use a userland phonetic matching library instead in %s on line %d +metaphone(): Argument #2 ($max_phonemes) must be less than or equal to %d +STR_MAX: metaphone(): Argument #2 ($max_phonemes) must be less than or equal to %d +STR_MAX+1: metaphone(): Argument #2 ($max_phonemes) must be less than or equal to %d +STR_MAX+2: metaphone(): Argument #2 ($max_phonemes) must be less than or equal to %d +SIZE_MAX-2: metaphone(): Argument #2 ($max_phonemes) must be less than or equal to %d +SIZE_MAX-1: metaphone(): Argument #2 ($max_phonemes) must be less than or equal to %d +SIZE_MAX: metaphone(): Argument #2 ($max_phonemes) must be less than or equal to %d diff --git a/ext/standard/tests/strings/sprintf_rope_optimization_004.phpt b/ext/standard/tests/strings/sprintf_rope_optimization_004.phpt index 27837e518a71..1e46a6570f77 100644 --- a/ext/standard/tests/strings/sprintf_rope_optimization_004.phpt +++ b/ext/standard/tests/strings/sprintf_rope_optimization_004.phpt @@ -7,22 +7,24 @@ gmp $a = new GMP("42"); $b = new GMP("-1337"); -$c = new GMP("999999999999999999999999999999999"); +$c = new GMP((string)PHP_INT_MAX); +$d = new GMP((string)PHP_INT_MIN); +$e = new GMP("999999999999999999999999999999999"); try { if (PHP_INT_SIZE == 8) { - var_dump(sprintf("%d/%d/%d/%s", $a, $b, $c, $c + 1)); - var_dump("42/-1337/2147483647/1000000000000000000000000000000000"); + var_dump(sprintf("%d/%d/%d/%d/%d/%s", $a, $b, $c, $d, $e, $e + 1)); + var_dump("42/-1337/2147483647/-2147483648/2147483647/1000000000000000000000000000000000"); } else { - var_dump("42/-1337/4089650035136921599/1000000000000000000000000000000000"); - var_dump(sprintf("%d/%d/%d/%s", $a, $b, $c, $c + 1)); + var_dump("42/-1337/9223372036854775807/-9223372036854775808/4089650035136921599/1000000000000000000000000000000000"); + var_dump(sprintf("%d/%d/%d/%d/%d/%s", $a, $b, $c, $d, $e, $e + 1)); } } catch (\Throwable $e) {echo $e, PHP_EOL; } echo PHP_EOL; echo "Done"; ?> --EXPECTF-- -string(63) "42/-1337/4089650035136921599/1000000000000000000000000000000000" -string(54) "42/-1337/2147483647/1000000000000000000000000000000000" +string(104) "42/-1337/9223372036854775807/-9223372036854775808/4089650035136921599/1000000000000000000000000000000000" +string(77) "42/-1337/2147483647/-2147483648/2147483647/1000000000000000000000000000000000" Done diff --git a/ext/standard/tests/strings/str_pad_variation1.phpt b/ext/standard/tests/strings/str_pad_variation1.phpt index cb71e61156fc..11471e58a46a 100644 --- a/ext/standard/tests/strings/str_pad_variation1.phpt +++ b/ext/standard/tests/strings/str_pad_variation1.phpt @@ -1,5 +1,7 @@ --TEST-- Test str_pad() function : usage variations - large values for '$pad_length' argument +--INI-- +memory_limit=1g --SKIPIF-- getMessage() . "\n"; } -$php_int_max_pad_length = PHP_INT_MAX; -var_dump( str_pad($input, $php_int_max_pad_length) ); - +// INT32_MAX +var_dump( str_pad($input, 2147483647) ); ?> --EXPECTF-- -*** Testing str_pad() function: with large value for for 'pad_length' argument *** +*** Testing str_pad() function: with large value for 'pad_length' argument *** str_pad(): Argument #2 ($length) must be of type int, float given Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d diff --git a/ext/standard/tests/strings/str_pad_variation2.phpt b/ext/standard/tests/strings/str_pad_variation2.phpt index 0bf8ad3ad80a..6377113a0e64 100644 --- a/ext/standard/tests/strings/str_pad_variation2.phpt +++ b/ext/standard/tests/strings/str_pad_variation2.phpt @@ -10,7 +10,7 @@ $string = chr(0).chr(255).chr(128).chr(234).chr(143); /* different pad_lengths */ $pad_lengths = [ - -PHP_INT_MAX, // huge negative value + (-2 ** (PHP_SYS_SIZE * 8 - 2)) << 1, // huge negative value -1, // negative value 0, // pad_length < sizeof(input_string) 9, // pad_length <= sizeof(input_string) diff --git a/ext/standard/tests/strings/str_pad_variation5.phpt b/ext/standard/tests/strings/str_pad_variation5.phpt index 1a65366c823c..fea795c8714f 100644 --- a/ext/standard/tests/strings/str_pad_variation5.phpt +++ b/ext/standard/tests/strings/str_pad_variation5.phpt @@ -4,26 +4,62 @@ Test str_pad() function : usage variations - unexpected large value for '$pad_le memory_limit=128M --SKIPIF-- = PHP_INT_SIZE) { + die("skip this test is for PHP_SYS_SIZE < PHP_INT_SIZE only"); +} if (getenv("USE_ZEND_ALLOC") === "0") { die("skip Zend MM disabled"); } ?> --FILE-- getMessage()}\n"; +} -//defining '$input' argument -$input = "Test string"; -$pad_length = PHP_INT_MAX - 16; /* zend_string header is 16 bytes */ -var_dump( str_pad($input, $pad_length) ); +/* str_pad() passes the padding count to zend_string_safe_alloc() as its + * unchecked third operand, which adds the header and the terminating NUL to + * it. Any resulting length above PHP_STRING_MAX_LENGTH wraps to a tiny + * allocation whose ZSTR_LEN is huge, and the padding is then memset() past + * the end of it. The whole window from the limit up to SIZE_MAX has to be + * refused, not just the values that exceed SIZE_MAX itself. */ +$sizeMax = 2 ** (PHP_SYS_SIZE * 8) - 1; + +$lengths = [ + 'STR_MAX+1' => PHP_STRING_MAX_LENGTH + 1, + 'STR_MAX+2' => PHP_STRING_MAX_LENGTH + 2, + 'STR_MAX+3' => PHP_STRING_MAX_LENGTH + 3, + 'SIZE_MAX-2' => $sizeMax - 2, + 'SIZE_MAX-1' => $sizeMax - 1, + 'SIZE_MAX' => $sizeMax, +]; + +foreach ($lengths as $label => $length) { + try { + var_dump( str_pad($input, $length, '-') ); + } catch (ValueError $e) { + echo "$label: {$e->getMessage()}\n"; + } +} +/* Below the limit the request is valid: it reaches the allocator and fails + * there rather than being refused or narrowed. (PHP_STRING_MAX_LENGTH itself + * is deliberately not used here: right at the top of the range the allocator + * reports the wrap of its own alignment rounding instead of the memory limit, + * and which of the two you get depends on the build.) */ +var_dump( str_pad($input, (2 ** (PHP_SYS_SIZE * 8-2) - 1 << 1) + 1) ); ?> --EXPECTF-- -*** Testing str_pad() function: with large value for for 'pad_length' argument *** +ValueError: str_pad(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+1: str_pad(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+2: str_pad(): Argument #2 ($length) must be less than or equal to %d +STR_MAX+3: str_pad(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX-2: str_pad(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX-1: str_pad(): Argument #2 ($length) must be less than or equal to %d +SIZE_MAX: str_pad(): Argument #2 ($length) must be less than or equal to %d Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d diff --git a/ext/standard/tests/strings/str_repeat_sizet.phpt b/ext/standard/tests/strings/str_repeat_sizet.phpt new file mode 100644 index 000000000000..e3d42a917675 --- /dev/null +++ b/ext/standard/tests/strings/str_repeat_sizet.phpt @@ -0,0 +1,47 @@ +--TEST-- +str_repeat(): $times must fit into size_t when zend_long is wider +--SKIPIF-- += PHP_INT_SIZE) { + die("skip this test is for PHP_SYS_SIZE < PHP_INT_SIZE only"); +} +?> +--FILE-- +getMessage(), "\n"; + } +} + +echo "-- negative still reports the plain message --\n"; +try { + var_dump(str_repeat('12', -1)); +} catch (ValueError $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +echo "-- small enough to be a valid size_t: not rejected, allocation decides --\n"; +var_dump(str_repeat('1', $sizeMax)); +?> +--EXPECTF-- +-- wider than size_t: rejected before narrowing -- +ValueError: str_repeat(): Argument #2 ($times) must be less than or equal to %d +ValueError: str_repeat(): Argument #2 ($times) must be less than or equal to %d +ValueError: str_repeat(): Argument #2 ($times) must be less than or equal to %d +-- negative still reports the plain message -- +ValueError: str_repeat(): Argument #2 ($times) must be greater than or equal to 0 +-- small enough to be a valid size_t: not rejected, allocation decides -- + +Fatal error: Possible integer overflow in memory allocation (%s) in %s on line %d diff --git a/ext/standard/tests/strings/str_split_variation6.phpt b/ext/standard/tests/strings/str_split_variation6.phpt index a8c1bbf1a927..706f5033ef76 100644 --- a/ext/standard/tests/strings/str_split_variation6.phpt +++ b/ext/standard/tests/strings/str_split_variation6.phpt @@ -19,6 +19,8 @@ $values = array ( 0x1A, //hexadecimal number 2147483647, //max positive integer number -2147483648, //min negative integer + PHP_INT_MAX, + (2 ** (PHP_SYS_SIZE * 8 - 2) - 1 << 1) + 1, // SSIZE_MAX ); //loop through each element of $values for 'split_length' @@ -144,3 +146,13 @@ array(1) { } -- Iteration 7 -- str_split(): Argument #2 ($length) must be greater than 0 +-- Iteration 8 -- +array(1) { + [0]=> + string(42) "This is a string with 123 & escape char \t" +} +-- Iteration 9 -- +array(1) { + [0]=> + string(42) "This is a string with 123 & escape char \t" +} diff --git a/ext/standard/tests/strings/stripos_error.phpt b/ext/standard/tests/strings/stripos_error.phpt index a3e805a709f9..ce1ab1ee085e 100644 --- a/ext/standard/tests/strings/stripos_error.phpt +++ b/ext/standard/tests/strings/stripos_error.phpt @@ -11,6 +11,12 @@ try { echo $exception->getMessage() . "\n"; } +try { + stripos("Hello World", "o", PHP_INT_MAX); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + echo "\n-- Offset before the start of the string --\n"; try { stripos("Hello World", "o", -12); @@ -18,6 +24,12 @@ try { echo $exception->getMessage() . "\n"; } +try { + stripos("Hello World", "o", PHP_INT_MIN); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + echo "*** Done ***"; ?> --EXPECT-- @@ -25,7 +37,9 @@ echo "*** Done ***"; -- Offset beyond the end of the string -- stripos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +stripos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) -- Offset before the start of the string -- stripos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +stripos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) *** Done *** diff --git a/ext/standard/tests/strings/strpos_error.phpt b/ext/standard/tests/strings/strpos_error.phpt new file mode 100644 index 000000000000..d37344da68a3 --- /dev/null +++ b/ext/standard/tests/strings/strpos_error.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test strpos() function : error conditions +--FILE-- +getMessage() . "\n"; +} + +try { + strpos("Hello World", "o", PHP_INT_MAX); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "\n-- Offset before the start of the string --\n"; +try { + strpos("Hello World", "o", -12); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + strpos("Hello World", "o", PHP_INT_MIN); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "*** Done ***"; +?> +--EXPECT-- +*** Testing strpos() function: error conditions *** + +-- Offset beyond the end of the string -- +strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) + +-- Offset before the start of the string -- +strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +*** Done *** diff --git a/ext/standard/tests/strings/strripos_error.phpt b/ext/standard/tests/strings/strripos_error.phpt new file mode 100644 index 000000000000..4dccc461cfbb --- /dev/null +++ b/ext/standard/tests/strings/strripos_error.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test strripos() function : error conditions +--FILE-- +getMessage() . "\n"; +} + +try { + strripos("Hello World", "o", PHP_INT_MAX); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "\n-- Offset before the start of the string --\n"; +try { + strripos("Hello World", "o", -12); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + strripos("Hello World", "o", PHP_INT_MIN); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "*** Done ***"; +?> +--EXPECT-- +*** Testing strripos() function: error conditions *** + +-- Offset beyond the end of the string -- +strripos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +strripos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) + +-- Offset before the start of the string -- +strripos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +strripos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +*** Done *** diff --git a/ext/standard/tests/strings/strrpos_error.phpt b/ext/standard/tests/strings/strrpos_error.phpt new file mode 100644 index 000000000000..85206eddfd7f --- /dev/null +++ b/ext/standard/tests/strings/strrpos_error.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test strrpos() function : error conditions +--FILE-- +getMessage() . "\n"; +} + +try { + strrpos("Hello World", "o", PHP_INT_MAX); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "\n-- Offset before the start of the string --\n"; +try { + strrpos("Hello World", "o", -12); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + strrpos("Hello World", "o", PHP_INT_MIN); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "*** Done ***"; +?> +--EXPECT-- +*** Testing strrpos() function: error conditions *** + +-- Offset beyond the end of the string -- +strrpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +strrpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) + +-- Offset before the start of the string -- +strrpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +strrpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +*** Done *** diff --git a/ext/standard/tests/strings/substr.phpt b/ext/standard/tests/strings/substr.phpt index c870e1de0fbd..d3fb68e64bef 100644 --- a/ext/standard/tests/strings/substr.phpt +++ b/ext/standard/tests/strings/substr.phpt @@ -59,6 +59,16 @@ echo "\n*** Omitting length or using NULL length ***\n"; var_dump (substr("abcdef" , 2) ); var_dump (substr("abcdef" , 2, NULL) ); +/* Very big offset */ +echo "\n*** Very big offset ***\n"; +var_dump (substr("abcdef" , PHP_INT_MAX) ); +var_dump (substr("abcdef" , PHP_INT_MIN) ); + +/* Very big length */ +echo "\n*** Very big length ***\n"; +var_dump (substr("abcdef" , 0, PHP_INT_MAX) ); +var_dump (substr("abcdef" , 0, PHP_INT_MIN) ); + echo"\nDone"; ?> @@ -173,4 +183,12 @@ string(4) "abcd" string(4) "cdef" string(4) "cdef" +*** Very big offset *** +string(0) "" +string(6) "abcdef" + +*** Very big length *** +string(6) "abcdef" +string(0) "" + Done diff --git a/ext/standard/tests/strings/substr_compare.phpt b/ext/standard/tests/strings/substr_compare.phpt index 51d093a65fa2..995588940f42 100644 --- a/ext/standard/tests/strings/substr_compare.phpt +++ b/ext/standard/tests/strings/substr_compare.phpt @@ -13,13 +13,25 @@ var_dump(substr_compare("abcde", "cd", 1, 2) < 0); var_dump(substr_compare("abcde", "abc", 5, 1)); var_dump(substr_compare("abcde", "abcdef", -10, 10) < 0); var_dump(substr_compare("abcde", "abc", 0, 0)); +var_dump(substr_compare("abc", "abcde", 0, PHP_INT_MAX)); echo "Test\n"; +var_dump(substr_compare("abcde", "abc", 0)); +var_dump(substr_compare("abcde", "abc", -100)); +var_dump(substr_compare("abcde", "abc", -PHP_INT_MAX)); + try { - substr_compare("abcde", "abc", 0, -1); + var_dump(substr_compare("abcde", "abc", 0, -1)); } catch (\ValueError $e) { echo $e->getMessage() . "\n"; } + +try { + var_dump(substr_compare("abcde", "abc", 0, PHP_INT_MIN)); +} catch (\ValueError $e) { + echo $e->getMessage() . "\n"; +} + var_dump(substr_compare("abcde", "abc", -1, NULL, -5) > 0); ?> --EXPECT-- @@ -33,6 +45,11 @@ bool(true) int(-1) bool(true) int(0) +int(-1) Test +int(1) +int(1) +int(1) +substr_compare(): Argument #4 ($length) must be greater than or equal to 0 substr_compare(): Argument #4 ($length) must be greater than or equal to 0 bool(true) diff --git a/ext/standard/tests/strings/substr_count_error.phpt b/ext/standard/tests/strings/substr_count_error.phpt index dc0d3d9834eb..614547577eec 100644 --- a/ext/standard/tests/strings/substr_count_error.phpt +++ b/ext/standard/tests/strings/substr_count_error.phpt @@ -20,6 +20,21 @@ try { echo $exception->getMessage() . "\n"; } +/* offset very big */ +try { + substr_count($str, 'b', PHP_INT_MAX); + echo "unexpected success\n"; +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + substr_count($str, 'b', PHP_INT_MIN); + echo "unexpected success\n"; +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + /* Using offset and length to go beyond the size of the string: Exception is expected, as length+offset > length of string */ try { @@ -35,6 +50,21 @@ try { echo $exception->getMessage() . "\n"; } +/* length very big */ +try { + substr_count($str, 'b', 0, PHP_INT_MAX); + echo "unexpected success\n"; +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + substr_count($str, 'b', 0, PHP_INT_MIN); + echo "unexpected success\n"; +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + echo "Done\n"; ?> @@ -42,6 +72,10 @@ echo "Done\n"; *** Testing error conditions *** substr_count(): Argument #3 ($offset) must be contained in argument #1 ($haystack) substr_count(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +substr_count(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +substr_count(): Argument #3 ($offset) must be contained in argument #1 ($haystack) +substr_count(): Argument #4 ($length) must be contained in argument #1 ($haystack) +substr_count(): Argument #4 ($length) must be contained in argument #1 ($haystack) substr_count(): Argument #4 ($length) must be contained in argument #1 ($haystack) substr_count(): Argument #4 ($length) must be contained in argument #1 ($haystack) Done diff --git a/ext/standard/tests/strings/substr_replace_large_offset_length.phpt b/ext/standard/tests/strings/substr_replace_large_offset_length.phpt new file mode 100644 index 000000000000..c6cb329d1218 --- /dev/null +++ b/ext/standard/tests/strings/substr_replace_large_offset_length.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test substr_replace() function : large offset & length +--FILE-- +getMessage(), "\n"; +} + +try { + var_dump(substr_replace('hello', 'X', PHP_INT_MIN)); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +echo "*** Very large length ***\n"; +try { + var_dump(substr_replace('hello', 'X', 0, PHP_INT_MAX)); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + var_dump(substr_replace('hello', 'X', 0, PHP_INT_MIN)); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +*** Very large offset *** +string(6) "helloX" +string(1) "X" +*** Very large length *** +string(1) "X" +string(6) "Xhello" diff --git a/main/streams/memory.c b/main/streams/memory.c index e76598ed0f46..ad3877f1e1b2 100644 --- a/main/streams/memory.c +++ b/main/streams/memory.c @@ -243,6 +243,8 @@ static int php_stream_memory_set_option(php_stream *stream, int option, int valu if (newsize < ms->fpos) { ms->fpos = newsize; } + } else if (UNEXPECTED(newsize > ZSTR_MAX_LEN)) { + return PHP_STREAM_OPTION_RETURN_ERR; } else { size_t old_size = ZSTR_LEN(ms->data); ms->data = zend_string_realloc(ms->data, newsize, 0); From 3cf4b57bac7903f460ea4859ac494b49cd717316 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Mon, 7 Jul 2025 07:36:51 +0200 Subject: [PATCH 11/23] Fix ext/date --- ext/date/php_date.c | 23 +++++++++++++++++------ ext/date/php_date.h | 4 ++-- ext/date/tests/idate_64bit.phpt | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 ext/date/tests/idate_64bit.phpt diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 1795d41b27ce..d49acb6fe23e 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -668,6 +668,18 @@ static const char *php_date_short_day_name(timelib_sll y, timelib_sll m, timelib } /* }}} */ +static zend_long date_date_to_zlong(timelib_time *d, int *error) +{ +#if SIZEOF_ZEND_LONG >= SIZEOF_LONG_LONG + if (error) { + *error = 0; + } + return (zend_long) d->sse; +#else + return timelib_date_to_int(d, error); +#endif +} + /* {{{ date_format - (gm)date helper */ static zend_string *date_format(const char *format, size_t format_len, const timelib_time *t, bool localtime) { @@ -881,7 +893,7 @@ static void php_date(INTERNAL_FUNCTION_PARAMETERS, bool localtime) } /* }}} */ -PHPAPI zend_string *php_format_date(const char *format, size_t format_len, time_t ts, bool localtime) /* {{{ */ +PHPAPI zend_string *php_format_date(const char *format, size_t format_len, zend_long ts, bool localtime) /* {{{ */ { timelib_time *t; timelib_tzinfo *tzi; @@ -907,7 +919,7 @@ PHPAPI zend_string *php_format_date(const char *format, size_t format_len, time_ /* }}} */ /* {{{ php_idate */ -PHPAPI bool php_idate(char format, time_t ts, bool localtime, int *result) +PHPAPI bool php_idate(char format, zend_long ts, bool localtime, int *result) { timelib_time *t; timelib_tzinfo *tzi; @@ -1135,7 +1147,7 @@ PHP_FUNCTION(strtotime) timelib_fill_holes(t, now, TIMELIB_NO_CLONE); timelib_update_ts(t, tzi); - ts = timelib_date_to_int(t, &epoch_does_not_fit_in_zend_long); + ts = date_date_to_zlong(t, &epoch_does_not_fit_in_zend_long); timelib_time_dtor(now); timelib_time_dtor(t); @@ -1218,8 +1230,7 @@ PHPAPI void php_mktime(INTERNAL_FUNCTION_PARAMETERS, bool gmt) } /* Clean up and return */ - ts = timelib_date_to_int(now, &epoch_does_not_fit_in_zend_long); - + ts = date_date_to_zlong(now, &epoch_does_not_fit_in_zend_long); if (epoch_does_not_fit_in_zend_long) { timelib_time_dtor(now); php_error_docref(NULL, E_WARNING, "Epoch doesn't fit in a PHP integer"); @@ -3968,7 +3979,7 @@ PHP_FUNCTION(date_timestamp_get) timelib_update_ts(dateobj->time, NULL); } - timestamp = timelib_date_to_int(dateobj->time, &epoch_does_not_fit_in_zend_long); + timestamp = date_date_to_zlong(dateobj->time, &epoch_does_not_fit_in_zend_long); if (epoch_does_not_fit_in_zend_long) { zend_throw_error(date_ce_date_range_error, "Epoch doesn't fit in a PHP integer"); diff --git a/ext/date/php_date.h b/ext/date/php_date.h index 97a49974f1a4..1cf2fd3fcacd 100644 --- a/ext/date/php_date.h +++ b/ext/date/php_date.h @@ -127,12 +127,12 @@ PHPAPI time_t php_time(void); /* Backwards compatibility wrapper */ PHPAPI zend_long php_parse_date(const char *string, zend_long *now); PHPAPI void php_mktime(INTERNAL_FUNCTION_PARAMETERS, bool gmt); -PHPAPI bool php_idate(char format, time_t ts, bool localtime, int *result); +PHPAPI bool php_idate(char format, zend_long ts, bool localtime, int *result); #define _php_strftime php_strftime PHPAPI void php_strftime(INTERNAL_FUNCTION_PARAMETERS, bool gm); -PHPAPI zend_string *php_format_date(const char *format, size_t format_len, time_t ts, bool localtime); +PHPAPI zend_string *php_format_date(const char *format, size_t format_len, zend_long ts, bool localtime); PHPAPI zend_string *php_format_date_obj(const char *format, size_t format_len, const php_date_obj *date_obj); /* Mechanism to set new TZ database */ diff --git a/ext/date/tests/idate_64bit.phpt b/ext/date/tests/idate_64bit.phpt new file mode 100644 index 000000000000..07ab742dfc2e --- /dev/null +++ b/ext/date/tests/idate_64bit.phpt @@ -0,0 +1,32 @@ +--TEST-- +idate() with timestamps outside the 32bit range +--SKIPIF-- + +--INI-- +date.timezone=UTC +--FILE-- + +--EXPECT-- + 0: 1970-1-1 0 (date: 1970-1-1 0) + 2147483647: 2038-1-19 3 (date: 2038-1-19 3) + 2147483648: 2038-1-19 3 (date: 2038-1-19 3) + 4102444800: 2100-1-1 0 (date: 2100-1-1 0) + 253402300799: 9999-12-31 23 (date: 9999-12-31 23) + -2147483649: 1901-12-13 20 (date: 1901-12-13 20) From 9960659764ec7c7725f85e29791bddf33b74db0b Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Mon, 7 Jul 2025 07:38:26 +0200 Subject: [PATCH 12/23] Fix ext/socket --- ext/sockets/sendrecvmsg.c | 2 +- ext/zend_test/test.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index cc268bd68b0a..a8f725d04647 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -314,7 +314,7 @@ PHP_FUNCTION(socket_cmsg_space) size_t size = entry->size + n * entry->var_el_size; size_t total_size = CMSG_SPACE(size); if (n > n_max /* zend_long overflow */ - || total_size > ZEND_LONG_MAX + || ZEND_SIZE_T_ZEND_LONG_OVFL(total_size) || total_size < size /* align overflow */) { zend_argument_value_error(3, "is too large"); RETURN_THROWS(); diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 82bfa8d38e33..13f1b0f9c3a3 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1608,7 +1608,7 @@ static ZEND_FUNCTION(zend_test_is_zend_ptr) Z_PARAM_LONG(addr); ZEND_PARSE_PARAMETERS_END(); - RETURN_BOOL(is_zend_ptr((void*)addr)); + RETURN_BOOL(is_zend_ptr((void*)(intptr_t)addr)); } static ZEND_FUNCTION(zend_test_log_err_debug) From aa98e086335d448b404fe6b6b38738883ac7de90 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Mon, 7 Jul 2025 07:39:43 +0200 Subject: [PATCH 13/23] Fix ext/gmp --- ext/gmp/gmp.c | 138 ++++++++++++++++++++++++---- ext/gmp/tests/construct_32bit.phpt | 36 ++++++++ ext/gmp/tests/construct_64bit.phpt | 55 +++++++++++ ext/gmp/tests/gmp_clrbit.phpt | 4 +- ext/gmp/tests/gmp_intval_64bit.phpt | 61 ++++++++++++ ext/gmp/tests/gmp_popcount.phpt | 1 - ext/gmp/tests/gmp_scan0.phpt | 2 +- ext/gmp/tests/gmp_scan1.phpt | 2 +- ext/gmp/tests/gmp_setbit.phpt | 2 +- ext/gmp/tests/gmp_setbit_long.phpt | 5 +- ext/gmp/tests/gmp_testbit.phpt | 4 +- 11 files changed, 283 insertions(+), 27 deletions(-) create mode 100644 ext/gmp/tests/construct_32bit.phpt create mode 100644 ext/gmp/tests/construct_64bit.phpt create mode 100644 ext/gmp/tests/gmp_intval_64bit.phpt diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index 4a81026d451b..d2ddef034a4d 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -104,6 +104,68 @@ PHP_GMP_API zend_class_entry *php_gmp_class_entry(void) { #define GET_GMP_FROM_ZVAL(zval) \ GET_GMP_OBJECT_FROM_OBJ(Z_OBJ_P(zval))->num +#define GMP_SI_MAX (GMP_NUMB_MAX >> 1) +#define GMP_SI_MIN (-(1LL << (GMP_NUMB_BITS - 1))) +#if GMP_NUMB_BITS < SIZEOF_ZEND_LONG*8 +static void gmp_set_zlong(mpz_t z, zend_long zlong) { + if (zlong <= GMP_SI_MAX && zlong >= GMP_SI_MIN) { + mpz_set_si(z, zlong); + } else { + /* mpz_import() takes no sign from the data, so the magnitude has to be + * formed first. Negating in unsigned arithmetic keeps ZEND_LONG_MIN + * well defined. */ + zend_ulong magnitude = zlong >= 0 ? (zend_ulong) zlong : -(zend_ulong) zlong; + + mpz_import(z, 1, 1, sizeof(magnitude), 0, 0, &magnitude); + if (zlong < 0) { + mpz_neg(z, z); + } + } +} + +static int gmp_fits_zlong_p(mpz_t z) { + int result = 1; + mpz_t min_max; + + if (mpz_cmp_si(z, GMP_SI_MAX) > 0) { + mpz_init(min_max); + gmp_set_zlong(min_max, ZEND_LONG_MAX); + result = mpz_cmp(z, min_max) <= 0; + mpz_clear(min_max); + } else if (mpz_cmp_si(z, GMP_SI_MIN) < 0) { + mpz_init(min_max); + gmp_set_zlong(min_max, ZEND_LONG_MIN); + result = mpz_cmp(z, min_max) >= 0; + mpz_clear(min_max); + } + + return result; +} + +static zend_long gmp_get_zlong(mpz_t z) { + zend_ulong result = 0; + mpz_t z_tmp; + + if (mpz_cmp_si(z, GMP_SI_MAX) <= 0 && mpz_cmp_si(z, GMP_SI_MIN) >= 0) { + return mpz_get_si(z); + } + + mpz_init(z_tmp); + /* The floored remainder is never negative, so this is the two's complement + * image of the low SIZEOF_ZEND_LONG * 8 bits. mpz_export() writes nothing + * at all when that is zero, hence the initialisation of result above. */ + mpz_fdiv_r_2exp(z_tmp, z, SIZEOF_ZEND_LONG * 8); + mpz_export(&result, NULL, 1, sizeof(result), 0, 0, z_tmp); + mpz_clear(z_tmp); + + return (zend_long) result; +} +#else +# define gmp_set_zlong(z, l) mpz_set_si(z, l) +# define gmp_fits_zlong_p(z) mpz_fits_si_p(z) +# define gmp_get_zlong(z) mpz_get_si(z) +#endif + static void gmp_strval(zval *result, mpz_t gmpnum, int base); static zend_result convert_zstr_to_gmp(mpz_t gmp_number, const zend_string *val, zend_long base, uint32_t arg_pos); @@ -127,7 +189,7 @@ static bool gmp_zend_parse_arg_into_mpz_ex( } if (Z_TYPE_P(arg) == IS_LONG) { - mpz_set_si(*destination_mpz_ptr, Z_LVAL_P(arg)); + gmp_set_zlong(*destination_mpz_ptr, Z_LVAL_P(arg)); return true; } @@ -143,7 +205,7 @@ static bool gmp_zend_parse_arg_into_mpz_ex( return false; } - mpz_set_si(*destination_mpz_ptr, lval); + gmp_set_zlong(*destination_mpz_ptr, lval); return true; } @@ -241,7 +303,7 @@ static zend_result gmp_cast_object(zend_object *readobj, zval *writeobj, int typ return SUCCESS; case IS_LONG: gmpnum = GET_GMP_OBJECT_FROM_OBJ(readobj)->num; - ZVAL_LONG(writeobj, mpz_get_si(gmpnum)); + ZVAL_LONG(writeobj, gmp_get_zlong(gmpnum)); return SUCCESS; case IS_DOUBLE: gmpnum = GET_GMP_OBJECT_FROM_OBJ(readobj)->num; @@ -249,8 +311,8 @@ static zend_result gmp_cast_object(zend_object *readobj, zval *writeobj, int typ return SUCCESS; case _IS_NUMBER: gmpnum = GET_GMP_OBJECT_FROM_OBJ(readobj)->num; - if (mpz_fits_si_p(gmpnum)) { - ZVAL_LONG(writeobj, mpz_get_si(gmpnum)); + if (gmp_fits_zlong_p(gmpnum)) { + ZVAL_LONG(writeobj, gmp_get_zlong(gmpnum)); } else { ZVAL_DOUBLE(writeobj, mpz_get_d(gmpnum)); } @@ -732,7 +794,7 @@ static zend_result gmp_initialize_number(mpz_ptr gmp_number, const zend_string * return convert_zstr_to_gmp(gmp_number, arg_str, base, 1); } - mpz_set_si(gmp_number, arg_l); + gmp_set_zlong(gmp_number, arg_l); return SUCCESS; } @@ -882,7 +944,7 @@ ZEND_FUNCTION(gmp_intval) GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum) ZEND_PARSE_PARAMETERS_END(); - RETVAL_LONG(mpz_get_si(gmpnum)); + RETVAL_LONG(gmp_get_zlong(gmpnum)); } /* }}} */ @@ -1610,8 +1672,13 @@ ZEND_FUNCTION(gmp_random_range) } /* }}} */ +#if SIZEOF_SIZE_T >= SIZEOF_ZEND_LONG +# define GMP_SAFE_BITINDEX_MAX ((mp_bitcnt_t)INT_MAX * GMP_NUMB_BITS) +#else +# define GMP_SAFE_BITINDEX_MAX ((mp_bitcnt_t)INT_MAX * GMP_NUMB_BITS - 1) +#endif static bool gmp_is_bit_index_valid(zend_long index) { - return index >= 0 && (index / GMP_NUMB_BITS < INT_MAX); + return index >= 0 && (zend_ulong)index <= GMP_SAFE_BITINDEX_MAX; } /* {{{ Sets or clear bit in a */ @@ -1627,7 +1694,7 @@ ZEND_FUNCTION(gmp_setbit) } if (!gmp_is_bit_index_valid(index)) { - zend_argument_value_error(2, "must be between 0 and %d * %d", INT_MAX, GMP_NUMB_BITS); + zend_argument_value_error(2, "must be between 0 and %lu", GMP_SAFE_BITINDEX_MAX); RETURN_THROWS(); } @@ -1653,7 +1720,7 @@ ZEND_FUNCTION(gmp_clrbit) } if (!gmp_is_bit_index_valid(index)) { - zend_argument_value_error(2, "must be between 0 and %d * %d", INT_MAX, GMP_NUMB_BITS); + zend_argument_value_error(2, "must be between 0 and %lu", GMP_SAFE_BITINDEX_MAX); RETURN_THROWS(); } @@ -1674,7 +1741,7 @@ ZEND_FUNCTION(gmp_testbit) ZEND_PARSE_PARAMETERS_END(); if (!gmp_is_bit_index_valid(index)) { - zend_argument_value_error(2, "must be between 0 and %d * %d", INT_MAX, GMP_NUMB_BITS); + zend_argument_value_error(2, "must be between 0 and %lu", GMP_SAFE_BITINDEX_MAX); RETURN_THROWS(); } @@ -1686,12 +1753,21 @@ ZEND_FUNCTION(gmp_testbit) ZEND_FUNCTION(gmp_popcount) { mpz_ptr gmpnum_a; + mp_bitcnt_t result; ZEND_PARSE_PARAMETERS_START(1, 1) GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_a) ZEND_PARSE_PARAMETERS_END(); - RETURN_LONG(mpz_popcount(gmpnum_a)); + result = mpz_popcount(gmpnum_a); + +#if SIZEOF_SIZE_T <= SIZEOF_ZEND_LONG + if (SIZE_MAX == result) { + RETURN_LONG(-1); + } +#endif + + RETURN_LONG(result); } /* }}} */ @@ -1699,13 +1775,22 @@ ZEND_FUNCTION(gmp_popcount) ZEND_FUNCTION(gmp_hamdist) { mpz_ptr gmpnum_a, gmpnum_b; + mp_bitcnt_t result; ZEND_PARSE_PARAMETERS_START(2, 2) GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_a) GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_b) ZEND_PARSE_PARAMETERS_END(); - RETURN_LONG(mpz_hamdist(gmpnum_a, gmpnum_b)); + result = mpz_hamdist(gmpnum_a, gmpnum_b); + +#if SIZEOF_SIZE_T <= SIZEOF_ZEND_LONG + if (SIZE_MAX == result) { + RETURN_LONG(-1); + } +#endif + + RETURN_LONG(result); } /* }}} */ @@ -1714,6 +1799,7 @@ ZEND_FUNCTION(gmp_scan0) { mpz_ptr gmpnum_a; zend_long start; + mp_bitcnt_t result; ZEND_PARSE_PARAMETERS_START(2, 2) GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_a) @@ -1721,11 +1807,19 @@ ZEND_FUNCTION(gmp_scan0) ZEND_PARSE_PARAMETERS_END(); if (!gmp_is_bit_index_valid(start)) { - zend_argument_value_error(2, "must be between 0 and %d * %d", INT_MAX, GMP_NUMB_BITS); + zend_argument_value_error(2, "must be between 0 and %lu", GMP_SAFE_BITINDEX_MAX); RETURN_THROWS(); } - RETURN_LONG(mpz_scan0(gmpnum_a, start)); + result = mpz_scan0(gmpnum_a, start); + +#if SIZEOF_SIZE_T <= SIZEOF_ZEND_LONG + if (SIZE_MAX == result) { + RETURN_LONG(-1); + } +#endif + + RETURN_LONG(result); } /* }}} */ @@ -1734,6 +1828,8 @@ ZEND_FUNCTION(gmp_scan1) { mpz_ptr gmpnum_a; zend_long start; + mp_bitcnt_t result; + ZEND_PARSE_PARAMETERS_START(2, 2) GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_a) @@ -1741,11 +1837,19 @@ ZEND_FUNCTION(gmp_scan1) ZEND_PARSE_PARAMETERS_END(); if (!gmp_is_bit_index_valid(start)) { - zend_argument_value_error(2, "must be between 0 and %d * %d", INT_MAX, GMP_NUMB_BITS); + zend_argument_value_error(2, "must be between 0 and %lu", GMP_SAFE_BITINDEX_MAX); RETURN_THROWS(); } - RETURN_LONG(mpz_scan1(gmpnum_a, start)); + result = mpz_scan1(gmpnum_a, start); + +#if SIZEOF_SIZE_T <= SIZEOF_ZEND_LONG + if (SIZE_MAX == result) { + RETURN_LONG(-1); + } +#endif + + RETURN_LONG(result); } /* }}} */ diff --git a/ext/gmp/tests/construct_32bit.phpt b/ext/gmp/tests/construct_32bit.phpt new file mode 100644 index 000000000000..581d1d5b3eba --- /dev/null +++ b/ext/gmp/tests/construct_32bit.phpt @@ -0,0 +1,36 @@ +--TEST-- +Constructor for GMP on 32bit int +--SKIPIF-- + +--EXTENSIONS-- +gmp +--FILE-- + + string(10) "2147483647" +} +object(GMP)#1 (1) { + ["num"]=> + string(10) "2147483647" +} +object(GMP)#1 (1) { + ["num"]=> + string(10) "2147483647" +} +object(GMP)#1 (1) { + ["num"]=> + string(11) "-2147483648" +} +object(GMP)#1 (1) { + ["num"]=> + string(11) "-2147483648" +} diff --git a/ext/gmp/tests/construct_64bit.phpt b/ext/gmp/tests/construct_64bit.phpt new file mode 100644 index 000000000000..3da2eb50f4a6 --- /dev/null +++ b/ext/gmp/tests/construct_64bit.phpt @@ -0,0 +1,55 @@ +--TEST-- +Constructor for GMP on 64bit int +--SKIPIF-- + +--EXTENSIONS-- +gmp +--FILE-- + + string(19) "9223372036854775807" +} +object(GMP)#1 (1) { + ["num"]=> + string(19) "9223372036854775807" +} +object(GMP)#1 (1) { + ["num"]=> + string(19) "9223372036854775807" +} +object(GMP)#1 (1) { + ["num"]=> + string(20) "-9223372036854775808" +} +object(GMP)#1 (1) { + ["num"]=> + string(20) "-9223372036854775808" +} +object(GMP)#1 (1) { + ["num"]=> + string(10) "2147483648" +} +object(GMP)#1 (1) { + ["num"]=> + string(11) "-2147483649" +} +object(GMP)#1 (1) { + ["num"]=> + string(11) "-3000000000" +} diff --git a/ext/gmp/tests/gmp_clrbit.phpt b/ext/gmp/tests/gmp_clrbit.phpt index 9dc63a87f5a5..f95964d1fc88 100644 --- a/ext/gmp/tests/gmp_clrbit.phpt +++ b/ext/gmp/tests/gmp_clrbit.phpt @@ -46,9 +46,9 @@ echo "Done\n"; ?> --EXPECTF-- string(1) "0" -ValueError: gmp_clrbit(): Argument #2 ($index) must be between 0 and %d * %d +ValueError: gmp_clrbit(): Argument #2 ($index) must be between 0 and %d string(2) "-1" -ValueError: gmp_clrbit(): Argument #2 ($index) must be between 0 and %d * %d +ValueError: gmp_clrbit(): Argument #2 ($index) must be between 0 and %d string(7) "1000000" string(7) "1000000" string(30) "238462734628347239571822592658" diff --git a/ext/gmp/tests/gmp_intval_64bit.phpt b/ext/gmp/tests/gmp_intval_64bit.phpt new file mode 100644 index 000000000000..555fad6c34a6 --- /dev/null +++ b/ext/gmp/tests/gmp_intval_64bit.phpt @@ -0,0 +1,61 @@ +--TEST-- +gmp_intval() on 64bit int +--SKIPIF-- + +--EXTENSIONS-- +gmp +--FILE-- + gmp_pow(2, 64), + '2 ** 65' => gmp_pow(2, 65), + '2 ** 128' => gmp_pow(2, 128), + '2 ** 64 + 1' => gmp_add(gmp_pow(2, 64), 1), +]; + +foreach ($fixtures as $label => $num) { + echo "$label: "; + var_dump(gmp_intval($num)); +} +?> +--EXPECT-- +2147483647: 2147483647 2147483647 +-2147483648: -2147483648 -2147483648 +2147483648: 2147483648 2147483648 +-2147483649: -2147483649 -2147483649 +3000000000: 3000000000 3000000000 +-3000000000: -3000000000 -3000000000 +4294967295: 4294967295 4294967295 +-4294967296: -4294967296 -4294967296 +9223372036854775807: 9223372036854775807 9223372036854775807 +-9223372036854775808: -9223372036854775808 -9223372036854775808 + +2 ** 64: int(0) +2 ** 65: int(0) +2 ** 128: int(0) +2 ** 64 + 1: int(1) diff --git a/ext/gmp/tests/gmp_popcount.phpt b/ext/gmp/tests/gmp_popcount.phpt index 3b15a659c5ed..b2e8beb5e9cf 100644 --- a/ext/gmp/tests/gmp_popcount.phpt +++ b/ext/gmp/tests/gmp_popcount.phpt @@ -4,7 +4,6 @@ gmp_popcount() basic tests gmp --FILE-- --EXPECTF-- -ValueError: gmp_scan0(): Argument #2 ($start) must be between 0 and %d * %d +ValueError: gmp_scan0(): Argument #2 ($start) must be between 0 and %d int(2) int(0) int(5) diff --git a/ext/gmp/tests/gmp_scan1.phpt b/ext/gmp/tests/gmp_scan1.phpt index 83a70bcba263..f5b6bb7ecf60 100644 --- a/ext/gmp/tests/gmp_scan1.phpt +++ b/ext/gmp/tests/gmp_scan1.phpt @@ -28,7 +28,7 @@ try { echo "Done\n"; ?> --EXPECTF-- -ValueError: gmp_scan1(): Argument #2 ($start) must be between 0 and %d * %d +ValueError: gmp_scan1(): Argument #2 ($start) must be between 0 and %d int(1) int(12) int(9) diff --git a/ext/gmp/tests/gmp_setbit.phpt b/ext/gmp/tests/gmp_setbit.phpt index 09ce16ada7fe..8c50d8be6d4a 100644 --- a/ext/gmp/tests/gmp_setbit.phpt +++ b/ext/gmp/tests/gmp_setbit.phpt @@ -52,7 +52,7 @@ echo "Done\n"; ?> --EXPECTF-- string(2) "-1" -ValueError: gmp_setbit(): Argument #2 ($index) must be between 0 and %d * %d +ValueError: gmp_setbit(): Argument #2 ($index) must be between 0 and %d string(1) "5" string(1) "1" string(1) "7" diff --git a/ext/gmp/tests/gmp_setbit_long.phpt b/ext/gmp/tests/gmp_setbit_long.phpt index e6d2dc262d59..909f7ee58bf8 100644 --- a/ext/gmp/tests/gmp_setbit_long.phpt +++ b/ext/gmp/tests/gmp_setbit_long.phpt @@ -3,7 +3,7 @@ gmp_setbit() with large index --EXTENSIONS-- gmp --SKIPIF-- - + 0 && $a < 0x8000000000; $a <<= 2) { $i = $a - 1; printf("%X\n", $i); @@ -41,5 +42,5 @@ FFFFFFFF 3FFFFFFFF FFFFFFFFF 3FFFFFFFFF -ValueError: gmp_setbit(): Argument #2 ($index) must be between 0 and %d * %d +ValueError: gmp_setbit(): Argument #2 ($index) must be between 0 and %d Done diff --git a/ext/gmp/tests/gmp_testbit.phpt b/ext/gmp/tests/gmp_testbit.phpt index fb6497df5463..d291b3d2afb1 100644 --- a/ext/gmp/tests/gmp_testbit.phpt +++ b/ext/gmp/tests/gmp_testbit.phpt @@ -48,12 +48,12 @@ var_dump(gmp_strval($n)); echo "Done\n"; ?> --EXPECTF-- -ValueError: gmp_testbit(): Argument #2 ($index) must be between 0 and %d * %d +ValueError: gmp_testbit(): Argument #2 ($index) must be between 0 and %d bool(false) bool(false) bool(false) bool(true) -ValueError: gmp_testbit(): Argument #2 ($index) must be between 0 and %d * %d +ValueError: gmp_testbit(): Argument #2 ($index) must be between 0 and %d bool(false) bool(true) string(7) "1000002" From 1fdc12a188c14d7c1b6ae6c5d8b2ee903eefd990 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Mon, 7 Jul 2025 07:40:12 +0200 Subject: [PATCH 14/23] Fix ext/shmop --- ext/shmop/shmop.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/shmop/shmop.c b/ext/shmop/shmop.c index 47ca1c2112ef..c4835f771450 100644 --- a/ext/shmop/shmop.c +++ b/ext/shmop/shmop.c @@ -182,6 +182,9 @@ PHP_FUNCTION(shmop_open) if (shmop->shmflg & IPC_CREAT && shmop->size < 1) { zend_argument_value_error(4, "must be greater than 0 for the \"c\" and \"n\" access modes"); goto err; + } else if (ZEND_LONG_SIZE_T_OVFL(shmop->size)) { + zend_argument_value_error(4, "must be less than or equal to %zu", SIZE_MAX); + goto err; } shmop->shmid = shmget(shmop->key, shmop->size, shmop->shmflg); @@ -196,7 +199,7 @@ PHP_FUNCTION(shmop_open) goto err; } - if (shm.shm_segsz > ZEND_LONG_MAX) { + if (ZEND_SIZE_T_ZEND_LONG_OVFL(shm.shm_segsz)) { zend_argument_value_error(4, "is too large"); goto err; } From bb719a22af6f5f45524caf82fc51c73825c4a73c Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Sun, 16 Nov 2025 11:46:08 +0100 Subject: [PATCH 15/23] Fix ext/opcache --- ext/opcache/jit/zend_jit_helpers.c | 11 +++++++++- ext/opcache/tests/gh9259_001_32bit.phpt | 20 +++++++++++++++++++ ...{gh9259_001.phpt => gh9259_001_64bit.phpt} | 4 ++-- ext/opcache/zend_file_cache.c | 2 +- ext/opcache/zend_shared_alloc.c | 10 +++++----- 5 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 ext/opcache/tests/gh9259_001_32bit.phpt rename ext/opcache/tests/{gh9259_001.phpt => gh9259_001_64bit.phpt} (74%) diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 660a17c05687..ca76708305e2 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1501,7 +1501,16 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, offset += (zend_long)ZSTR_LEN(s); } - if ((size_t)offset >= ZSTR_LEN(s)) { + if (ZEND_LONG_GTE_SIZE_T(offset, ZSTR_LEN(s))) { +#if SIZEOF_SIZE_T < SIZEOF_ZEND_LONG + if (UNEXPECTED(offset >= (zend_long) ZSTR_MAX_LEN)) { + zend_throw_error(NULL, "String size overflow"); + if (result) { + ZVAL_UNDEF(result); + } + return; + } +#endif /* Extend string if needed */ zend_long old_len = ZSTR_LEN(s); ZVAL_NEW_STR(str, zend_string_extend(s, (size_t)offset + 1, 0)); diff --git a/ext/opcache/tests/gh9259_001_32bit.phpt b/ext/opcache/tests/gh9259_001_32bit.phpt new file mode 100644 index 000000000000..767430fec4c0 --- /dev/null +++ b/ext/opcache/tests/gh9259_001_32bit.phpt @@ -0,0 +1,20 @@ +--TEST-- +Bug GH-9259 001 (Setting opcache.interned_strings_buffer to a very high value leads to corruption of shm) - 32bit +--EXTENSIONS-- +opcache +--SKIPIF-- + +--INI-- +opcache.interned_strings_buffer=131072 +opcache.log_verbosity_level=2 +opcache.enable_cli=1 +--FILE-- + +--EXPECTF-- +%sWarning opcache.interned_strings_buffer must be less than or equal to 4095, 131072 given%s + +OK diff --git a/ext/opcache/tests/gh9259_001.phpt b/ext/opcache/tests/gh9259_001_64bit.phpt similarity index 74% rename from ext/opcache/tests/gh9259_001.phpt rename to ext/opcache/tests/gh9259_001_64bit.phpt index d98cbfb891d7..f1f5773073e0 100644 --- a/ext/opcache/tests/gh9259_001.phpt +++ b/ext/opcache/tests/gh9259_001_64bit.phpt @@ -1,9 +1,9 @@ --TEST-- -Bug GH-9259 001 (Setting opcache.interned_strings_buffer to a very high value leads to corruption of shm) +Bug GH-9259 001 (Setting opcache.interned_strings_buffer to a very high value leads to corruption of shm) - 64bit --EXTENSIONS-- opcache --SKIPIF-- - + --INI-- opcache.interned_strings_buffer=131072 opcache.log_verbosity_level=2 diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 0b6cabe42015..871fd66109bb 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -267,7 +267,7 @@ static void *zend_file_cache_serialize_interned(zend_string *str, } len = ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(ZSTR_LEN(str))); - ret = (void*)(info->str_size | Z_UL(1)); + ret = (void*)(info->str_size | 1); zend_shared_alloc_register_xlat_entry(str, ret); zend_string *s = (zend_string*)ZCG(mem); diff --git a/ext/opcache/zend_shared_alloc.c b/ext/opcache/zend_shared_alloc.c index 3d6d2a840c33..79542cd314fc 100644 --- a/ext/opcache/zend_shared_alloc.c +++ b/ext/opcache/zend_shared_alloc.c @@ -417,7 +417,7 @@ static zend_always_inline zend_ulong zend_rotr3(zend_ulong key) int zend_shared_memdup_size(void *source, size_t size) { void *old_p; - zend_ulong key = (zend_ulong)source; + zend_ulong key = (zend_ulong)(uintptr_t)source; key = zend_rotr3(key); if ((old_p = zend_hash_index_find_ptr(&ZCG(xlat_table), key)) != NULL) { @@ -434,7 +434,7 @@ static zend_always_inline void *_zend_shared_memdup(void *source, size_t size, b zend_ulong key; if (get_xlat) { - key = (zend_ulong)source; + key = (zend_ulong)(uintptr_t)source; key = zend_rotr3(key); if ((old_p = zend_hash_index_find_ptr(&ZCG(xlat_table), key)) != NULL) { /* we already duplicated this pointer */ @@ -446,7 +446,7 @@ static zend_always_inline void *_zend_shared_memdup(void *source, size_t size, b memcpy(retval, source, size); if (set_xlat) { if (!get_xlat) { - key = (zend_ulong)source; + key = (zend_ulong)(uintptr_t)source; key = zend_rotr3(key); } zend_hash_index_add_new_ptr(&ZCG(xlat_table), key, retval); @@ -589,7 +589,7 @@ void zend_shared_alloc_restore_xlat_table(uint32_t checkpoint) void zend_shared_alloc_register_xlat_entry(const void *key_pointer, const void *value) { - zend_ulong key = (zend_ulong)key_pointer; + zend_ulong key = (zend_ulong)(uintptr_t)key_pointer; key = zend_rotr3(key); zend_hash_index_add_new_ptr(&ZCG(xlat_table), key, (void*)value); @@ -598,7 +598,7 @@ void zend_shared_alloc_register_xlat_entry(const void *key_pointer, const void * void *zend_shared_alloc_get_xlat_entry(const void *key_pointer) { void *retval; - zend_ulong key = (zend_ulong)key_pointer; + zend_ulong key = (zend_ulong)(uintptr_t)key_pointer; key = zend_rotr3(key); if ((retval = zend_hash_index_find_ptr(&ZCG(xlat_table), key)) == NULL) { From 546eb0f75626614131a3e36711ade376ee1061cf Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Fri, 11 Sep 2026 07:23:44 +0200 Subject: [PATCH 16/23] Disable JIT when zend_long is wider than the platform word The IR x86 backend emits 64bit integer instructions only when it is built for X64. On a 32bit target the 8 byte cases of the type dispatch are not compiled at all, so an 8 byte operand reaches the default label. In a debug build that is IR_ASSERT(0) and the process aborts; in a release build IR_ASSERT() expands to nothing and control falls through into the 1 byte case, which emits a byte sized instruction with the immediate masked to 0xff. The JIT would silently generate wrong code. Supporting this properly means adding 64bit arithmetic over register pairs to 21 emission paths in the x86 backend, which belongs upstream in IR rather than here. Until then refuse to enable the JIT, in the same place and shape as the existing Apple Silicon ZTS check. The condition is false wherever zend_long matches the platform word, so every shipping platform is unaffected. --- ext/opcache/jit/zend_jit.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index e91da6aeb8d3..fd8529dd415f 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -3745,6 +3745,18 @@ int zend_jit_check_support(void) { int i; +#if SIZEOF_ZEND_LONG > SIZEOF_SIZE_T + /* The IR x86 backend emits 64bit integer instructions only when built for + * X64. With a zend_long wider than the target word the 8 byte cases are + * absent, so an emit falls through to the byte sized instruction and the + * JIT would silently produce wrong code. */ + zend_accel_error(ACCEL_LOG_WARNING, + "JIT is not supported when zend_long is wider than the platform word. JIT disabled."); + JIT_G(enabled) = 0; + JIT_G(on) = 0; + return FAILURE; +#endif + #ifdef ZEND_JIT_USE_APPLE_MAP_JIT if (!pthread_jit_write_protect_supported_np()) { zend_accel_error(ACCEL_LOG_WARNING, From 741233b05286154867146cbefd745f894bea17f8 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Sun, 16 Nov 2025 12:40:32 +0100 Subject: [PATCH 17/23] Fix sapi/phpdbg --- sapi/phpdbg/phpdbg_bp.c | 8 ++++---- sapi/phpdbg/phpdbg_btree.c | 2 +- sapi/phpdbg/phpdbg_prompt.c | 2 +- sapi/phpdbg/phpdbg_watch.c | 32 ++++++++++++++++---------------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/sapi/phpdbg/phpdbg_bp.c b/sapi/phpdbg/phpdbg_bp.c index 1233f0430121..addd56ea454c 100644 --- a/sapi/phpdbg/phpdbg_bp.c +++ b/sapi/phpdbg/phpdbg_bp.c @@ -513,7 +513,7 @@ PHPDBG_API int phpdbg_resolve_op_array_break(phpdbg_breakopline_t *brake, zend_o opline_break.disabled = 0; opline_break.hits = 0; opline_break.id = brake->id; - opline_break.opline = brake->opline = (zend_ulong)(op_array->opcodes + brake->opline_num); + opline_break.opline = brake->opline = ZEND_PTR_TO_ZEND_ULONG(op_array->opcodes + brake->opline_num); opline_break.name = NULL; opline_break.base = brake; if (op_array->scope) { @@ -809,7 +809,7 @@ PHPDBG_API void phpdbg_set_breakpoint_opline_ex(phpdbg_opline_ptr_t opline) /* { PHPDBG_G(flags) |= PHPDBG_HAS_OPLINE_BP; PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_OPLINE); - new_break.opline = (zend_ulong) opline; + new_break.opline = ZEND_PTR_TO_ZEND_ULONG(opline); new_break.base = NULL; zend_hash_index_update_mem(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], (zend_ulong)(uintptr_t) opline, &new_break, sizeof(phpdbg_breakline_t)); @@ -817,7 +817,7 @@ PHPDBG_API void phpdbg_set_breakpoint_opline_ex(phpdbg_opline_ptr_t opline) /* { phpdbg_notice("Breakpoint #%d added at #"ZEND_ULONG_FMT, new_break.id, new_break.opline); PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]); } else { - phpdbg_error("Breakpoint exists for opline #"ZEND_ULONG_FMT, (zend_ulong) opline); + phpdbg_error("Breakpoint exists for opline #"ZEND_ULONG_FMT, ZEND_PTR_TO_ZEND_ULONG(opline)); } } /* }}} */ @@ -1068,7 +1068,7 @@ static inline bool phpdbg_find_breakpoint_param(phpdbg_param_t *param, zend_exec } break; case ADDR_PARAM: { - return ((zend_ulong)(phpdbg_opline_ptr_t)execute_data->opline == param->addr); + return (ZEND_PTR_TO_ZEND_ULONG((phpdbg_opline_ptr_t)execute_data->opline) == param->addr); } break; default: { diff --git a/sapi/phpdbg/phpdbg_btree.c b/sapi/phpdbg/phpdbg_btree.c index 788e9b464c14..5e58f63ce09c 100644 --- a/sapi/phpdbg/phpdbg_btree.c +++ b/sapi/phpdbg/phpdbg_btree.c @@ -253,7 +253,7 @@ void phpdbg_btree_branch_dump(phpdbg_btree_branch *branch, zend_ulong depth) { phpdbg_btree_branch_dump(branch->branches[0], depth); phpdbg_btree_branch_dump(branch->branches[1], depth); } else { - fprintf(stderr, "%p: %p\n", (void *) branch->result.idx, branch->result.ptr); + fprintf(stderr, ZEND_ULONG_FMT": %p\n", branch->result.idx, branch->result.ptr); } } } diff --git a/sapi/phpdbg/phpdbg_prompt.c b/sapi/phpdbg/phpdbg_prompt.c index 88afd1b3752e..55d28b519752 100644 --- a/sapi/phpdbg/phpdbg_prompt.c +++ b/sapi/phpdbg/phpdbg_prompt.c @@ -1712,7 +1712,7 @@ void phpdbg_execute_ex(zend_execute_data *execute_data) /* {{{ */ /* perform seek operation */ if ((PHPDBG_G(flags) & PHPDBG_SEEK_MASK) && !(PHPDBG_G(flags) & PHPDBG_IN_EVAL)) { /* current address */ - zend_ulong address = (zend_ulong) execute_data->opline; + zend_ulong address = ZEND_PTR_TO_ZEND_ULONG(execute_data->opline); if (PHPDBG_G(seek_ex) != execute_data) { if (PHPDBG_G(flags) & PHPDBG_IS_STEPPING) { diff --git a/sapi/phpdbg/phpdbg_watch.c b/sapi/phpdbg/phpdbg_watch.c index a2e39e15a6ca..f8129515095b 100644 --- a/sapi/phpdbg/phpdbg_watch.c +++ b/sapi/phpdbg/phpdbg_watch.c @@ -226,7 +226,7 @@ void phpdbg_print_watch_diff(phpdbg_watchtype type, zend_string *name, void *old /* ### LOW LEVEL WATCHPOINT HANDLING ### */ static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(phpdbg_btree *tree, void *addr) { phpdbg_watchpoint_t *watch; - phpdbg_btree_result *result = phpdbg_btree_find_closest(tree, (zend_ulong) phpdbg_get_page_boundary(addr) + phpdbg_pagesize - 1); + phpdbg_btree_result *result = phpdbg_btree_find_closest(tree, ZEND_PTR_TO_ZEND_ULONG(phpdbg_get_page_boundary(addr)) + phpdbg_pagesize - 1); if (result == NULL) { return NULL; @@ -343,14 +343,14 @@ void *phpdbg_watchpoint_userfaultfd_thread(void *phpdbg_globals_ptr) { /* ### REGISTER WATCHPOINT ### To be used only by watch element and collision managers ### */ static inline void phpdbg_store_watchpoint_btree(phpdbg_watchpoint_t *watch) { #if ZEND_DEBUG - phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr); + phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), ZEND_PTR_TO_ZEND_ULONG(watch->addr.ptr)); ZEND_ASSERT(res == NULL || res->ptr == watch); #endif - phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr, watch); + phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), ZEND_PTR_TO_ZEND_ULONG(watch->addr.ptr), watch); } static inline void phpdbg_remove_watchpoint_btree(phpdbg_watchpoint_t *watch) { - phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr); + phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), ZEND_PTR_TO_ZEND_ULONG(watch->addr.ptr)); } /* ### SET WATCHPOINT ADDR ### To be used only by watch element and collision managers ### */ @@ -513,7 +513,7 @@ void phpdbg_watch_parent_ht(phpdbg_watch_element *element); static bool phpdbg_watch_element_collides(void *addr, phpdbg_watchtype type, phpdbg_watch_element *element) { phpdbg_watch_element *old; - phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) addr); + phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), ZEND_PTR_TO_ZEND_ULONG(addr)); if (res) { phpdbg_watchpoint_t *watch = res->ptr; @@ -544,7 +544,7 @@ phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdb if (is_new) { *is_new = true; } - if ((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL) { + if ((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), ZEND_PTR_TO_ZEND_ULONG(watch->addr.ptr))) == NULL) { phpdbg_watchpoint_t *mem = emalloc(sizeof(*mem)); *mem = *watch; watch = mem; @@ -728,12 +728,12 @@ void phpdbg_watch_parent_ht(phpdbg_watch_element *element) { phpdbg_btree_result *res; phpdbg_watch_ht_info *hti; ZEND_ASSERT(element->parent_container); - if (!(res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container))) { + if (!(res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), ZEND_PTR_TO_ZEND_ULONG(element->parent_container)))) { hti = emalloc(sizeof(*hti)); hti->ht = element->parent_container; zend_hash_init(&hti->watches, 0, NULL, ZVAL_PTR_DTOR, 0); - phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht, hti); + phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), ZEND_PTR_TO_ZEND_ULONG(hti->ht), hti); phpdbg_set_addr_watchpoint(HT_GET_DATA_ADDR(hti->ht), HT_HASH_SIZE(hti->ht->nTableMask), &hti->hash_watch); hti->hash_watch.type = WATCH_ON_HASHDATA; @@ -751,7 +751,7 @@ void phpdbg_watch_parent_ht(phpdbg_watch_element *element) { void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) { if (element->flags & PHPDBG_WATCH_HT_REGISTERED) { - phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container); + phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), ZEND_PTR_TO_ZEND_ULONG(element->parent_container)); ZEND_ASSERT(element->parent_container); element->flags &= ~PHPDBG_WATCH_HT_REGISTERED; if (res) { @@ -759,7 +759,7 @@ void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) { if (zend_hash_num_elements(&hti->watches) == 1) { zend_hash_destroy(&hti->watches); - phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht); + phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), ZEND_PTR_TO_ZEND_ULONG(hti->ht)); phpdbg_remove_watchpoint_btree(&hti->hash_watch); phpdbg_deactivate_watchpoint(&hti->hash_watch); efree(hti); @@ -1141,7 +1141,7 @@ void phpdbg_check_watchpoint(phpdbg_watchpoint_t *watch) { zval *zv; ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) { if (element->flags & PHPDBG_WATCH_RECURSIVE) { - phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) HT_WATCH_HT(watch)); + phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), ZEND_PTR_TO_ZEND_ULONG(HT_WATCH_HT(watch))); phpdbg_watch_ht_info *hti = res ? res->ptr : NULL; ZEND_HASH_REVERSE_FOREACH_KEY_VAL(HT_WATCH_HT(watch), idx, str, zv) { @@ -1254,7 +1254,7 @@ void phpdbg_reenable_memory_watches(void) { res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page + phpdbg_pagesize - 1); if (res) { watch = res->ptr; - if ((char *) page < (char *) watch->addr.ptr + watch->size) { + if ((char *) ZEND_ULONG_TO_PTR(page) < (char *) watch->addr.ptr + watch->size) { #ifdef HAVE_USERFAULTFD_WRITEFAULT if (PHPDBG_G(watch_userfaultfd)) { struct uffdio_writeprotect protect = { @@ -1268,7 +1268,7 @@ void phpdbg_reenable_memory_watches(void) { } else #endif { - mprotect((void *) page, phpdbg_pagesize, PROT_READ); + mprotect(ZEND_ULONG_TO_PTR(page), phpdbg_pagesize, PROT_READ); } } } @@ -1301,7 +1301,7 @@ int phpdbg_print_changed_zvals(void) { } if ((res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page - 1))) { watch = res->ptr; - if ((char *) page < (char *) watch->addr.ptr + watch->size) { + if ((char *) ZEND_ULONG_TO_PTR(page) < (char *) watch->addr.ptr + watch->size) { phpdbg_check_watchpoint(watch); } } @@ -1328,7 +1328,7 @@ void phpdbg_watch_efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { /* only do expensive checks if there are any watches at all */ if (zend_hash_num_elements(&PHPDBG_G(watch_elements))) { - if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr))) { + if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), ZEND_PTR_TO_ZEND_ULONG(ptr)))) { phpdbg_watchpoint_t *watch = result->ptr; if (watch->type != WATCH_ON_HASHDATA) { phpdbg_remove_watchpoint(watch); @@ -1348,7 +1348,7 @@ void phpdbg_watch_efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { } /* special case watchpoints as they aren't on ptr but on ptr + HT_WATCH_OFFSET */ - if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), HT_WATCH_OFFSET + (zend_ulong) ptr))) { + if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), HT_WATCH_OFFSET + ZEND_PTR_TO_ZEND_ULONG(ptr)))) { phpdbg_watchpoint_t *watch = result->ptr; if (watch->type == WATCH_ON_HASHTABLE) { phpdbg_remove_watchpoint(watch); From 37a34fc8ce5de24efdcc78539dec6ce7cef98929 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Wed, 9 Sep 2026 07:31:23 +0200 Subject: [PATCH 18/23] Assert zend_string_*_alloc lengths stay within ZSTR_MAX_LEN zend_string_alloc() and zend_string_safe_alloc() add the header and the terminating NUL to the requested length without an overflow check, so a length above ZSTR_MAX_LEN wraps to a tiny allocation carrying a huge ZSTR_LEN. That bound has always been the callers' responsibility, but it was neither documented nor enforced anywhere. Assert it in the four allocators that take a length, making the contract executable in debug builds at no cost to release builds. --- Zend/zend_string.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Zend/zend_string.h b/Zend/zend_string.h index a538b70c2c49..3789a56a9934 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -203,6 +203,7 @@ static zend_always_inline uint32_t zend_string_delref(zend_string *s) static zend_always_inline zend_string *zend_string_alloc(size_t len, bool persistent) { + ZEND_ASSERT(len <= ZSTR_MAX_LEN); zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent); GC_SET_REFCOUNT(ret, 1); @@ -214,6 +215,7 @@ static zend_always_inline zend_string *zend_string_alloc(size_t len, bool persis static zend_always_inline zend_string *zend_string_safe_alloc(size_t n, size_t m, size_t l, bool persistent) { + ZEND_ASSERT(l <= ZSTR_MAX_LEN); zend_string *ret = (zend_string *)safe_pemalloc(n, m, ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(l)), persistent); GC_SET_REFCOUNT(ret, 1); @@ -277,6 +279,7 @@ static zend_always_inline zend_string *zend_string_realloc(zend_string *s, size_ { zend_string *ret; + ZEND_ASSERT(len <= ZSTR_MAX_LEN); if (!ZSTR_IS_INTERNED(s)) { if (EXPECTED(GC_REFCOUNT(s) == 1)) { ret = (zend_string *)perealloc(s, ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent); @@ -298,6 +301,7 @@ static zend_always_inline zend_string *zend_string_extend(zend_string *s, size_t zend_string *ret; ZEND_ASSERT(len >= ZSTR_LEN(s)); + ZEND_ASSERT(len <= ZSTR_MAX_LEN); if (!ZSTR_IS_INTERNED(s)) { if (EXPECTED(GC_REFCOUNT(s) == 1)) { ret = (zend_string *)perealloc(s, ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent); From 787624d195bdae78ecc1416d890d1bdf072ac37b Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Wed, 9 Sep 2026 21:28:29 +0200 Subject: [PATCH 19/23] Fix strncmp() and strncasecmp() strncmp() and strncasecmp() pass $length straight into the size_t parameter of zend_binary_strncmp()/zend_binary_strncasecmp(). Where zend_long is wider than size_t the value is narrowed, so a length above SIZE_MAX compares fewer bytes than was asked for and reports equality for operands that differ further along: SIZE_MAX + 1 becomes 0 and SIZE_MAX + 3 becomes 2. Clamp to SIZE_MAX instead, as substr_compare() already does. Both comparators reduce the length to MIN(length, MIN(len1, len2)) internally, so every value at or above the operand lengths behaves alike and no new error is needed. --- Zend/tests/strncmp_length_sizet.phpt | 41 ++++++++++++++++++++++++++++ Zend/zend_builtin_functions.c | 10 +++++-- 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/strncmp_length_sizet.phpt diff --git a/Zend/tests/strncmp_length_sizet.phpt b/Zend/tests/strncmp_length_sizet.phpt new file mode 100644 index 000000000000..8f719408669c --- /dev/null +++ b/Zend/tests/strncmp_length_sizet.phpt @@ -0,0 +1,41 @@ +--TEST-- +strncmp()/strncasecmp() $length must not be narrowed to size_t +--SKIPIF-- += PHP_INT_SIZE) { + die("skip size_t is not narrower than zend_long on this platform"); +} +?> +--FILE-- + 2, + '3' => 3, + 'SIZE_MAX' => $sizeMax, + 'SIZE_MAX+1' => $sizeMax + 1, + 'SIZE_MAX+3' => $sizeMax + 3, + 'INT_MAX' => PHP_INT_MAX, +]; + +foreach ($lengths as $label => $length) { + echo "$label: "; + printf("strncmp=%d strncasecmp=%d\n", + strncmp('abc', 'abd', $length), + strncasecmp('ABC', 'abd', $length)); +} +?> +--EXPECT-- +2: strncmp=0 strncasecmp=0 +3: strncmp=-1 strncasecmp=-1 +SIZE_MAX: strncmp=-1 strncasecmp=-1 +SIZE_MAX+1: strncmp=-1 strncasecmp=-1 +SIZE_MAX+3: strncmp=-1 strncasecmp=-1 +INT_MAX: strncmp=-1 strncasecmp=-1 diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index e02e0afe2bfc..3a1b0c60805c 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -404,6 +404,7 @@ ZEND_FUNCTION(strncmp) { zend_string *s1, *s2; zend_long len; + size_t cmp_len; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_STR(s1) @@ -416,7 +417,9 @@ ZEND_FUNCTION(strncmp) RETURN_THROWS(); } - RETURN_LONG(zend_binary_strncmp(ZSTR_VAL(s1), ZSTR_LEN(s1), ZSTR_VAL(s2), ZSTR_LEN(s2), len)); + cmp_len = ZEND_LONG_SIZE_T_OVFL(len) ? SIZE_MAX : (size_t) len; + + RETURN_LONG(zend_binary_strncmp(ZSTR_VAL(s1), ZSTR_LEN(s1), ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len)); } /* }}} */ @@ -439,6 +442,7 @@ ZEND_FUNCTION(strncasecmp) { zend_string *s1, *s2; zend_long len; + size_t cmp_len; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_STR(s1) @@ -451,7 +455,9 @@ ZEND_FUNCTION(strncasecmp) RETURN_THROWS(); } - RETURN_LONG(zend_binary_strncasecmp(ZSTR_VAL(s1), ZSTR_LEN(s1), ZSTR_VAL(s2), ZSTR_LEN(s2), len)); + cmp_len = ZEND_LONG_SIZE_T_OVFL(len) ? SIZE_MAX : (size_t) len; + + RETURN_LONG(zend_binary_strncasecmp(ZSTR_VAL(s1), ZSTR_LEN(s1), ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len)); } /* }}} */ From 4d71c14a4091085e220e404de9938321be4ae99f Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Fri, 11 Sep 2026 07:23:27 +0200 Subject: [PATCH 20/23] Add LINUX_X32_INT64 CI job Runs the test suite on a 32bit build whose zend_long is 64bit, the configuration --enable-zend-int64 makes possible. The job mirrors LINUX_X32 and adds --enable-zend-int64. It builds with -mfpmath=sse. On i386 the x87 unit loads a 64bit integer exactly into an 80bit register, so (double) a * (double) b multiplies an operand that was never rounded to binary64 and the product differs from every 64bit platform in the last place. SSE math rounds the operand first and keeps the results identical. The x87 precision control word that zend_init_fpu() sets does not help here: it limits the mantissa of the arithmetic, not the exactness of the integer load. configure-x32 gained a cflags input rather than growing a second copy of the action; LINUX_X32 keeps its previous flags through the default. --- .github/actions/configure-x32/action.yml | 7 +- .github/matrix.php | 8 +++ .github/workflows/test-suite.yml | 87 ++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/.github/actions/configure-x32/action.yml b/.github/actions/configure-x32/action.yml index a5c5df4f7971..8b9ddd318923 100644 --- a/.github/actions/configure-x32/action.yml +++ b/.github/actions/configure-x32/action.yml @@ -3,6 +3,9 @@ inputs: configurationParameters: default: '' required: false + cflags: + default: '-m32 -msse2' + required: false runs: using: composite steps: @@ -12,8 +15,8 @@ runs: export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/lib/i386-linux-gnu/pkgconfig" ./buildconf --force - export CFLAGS="-m32 -msse2" - export CXXFLAGS="-m32 -msse2" + export CFLAGS="${{ inputs.cflags }}" + export CXXFLAGS="${{ inputs.cflags }}" export LDFLAGS=-L/usr/lib/i386-linux-gnu ./configure ${{ inputs.configurationParameters }} \ --enable-option-checking=fatal \ diff --git a/.github/matrix.php b/.github/matrix.php index ee9cb4eb05ab..7ddf640509b9 100644 --- a/.github/matrix.php +++ b/.github/matrix.php @@ -57,6 +57,7 @@ function select_jobs($repository, $trigger, $nightly, $labels, $php_version, $re $test_libmysqlclient = in_array('CI: libmysqlclient', $labels, true); $test_linux_ppc64 = in_array('CI: Linux PPC64', $labels, true); $test_linux_x32 = in_array('CI: Linux X32', $labels, true); + $test_linux_x32_int64 = in_array('CI: Linux X32 INT64', $labels, true); $test_linux_x64 = in_array('CI: Linux X64', $labels, true); $test_macos = in_array('CI: macOS', $labels, true); $test_msan = in_array('CI: MSAN', $labels, true); @@ -123,6 +124,13 @@ function select_jobs($repository, $trigger, $nightly, $labels, $php_version, $re ? ['debug' => [true, false], 'zts' => [true, false]] : ['debug' => [true], 'zts' => [true]]; } + // 32bit userland with a 64bit zend_long. Version gated because + // --enable-zend-int64 does not exist on the older branches. + if (version_compare($php_version, '8.6', '>=') && ($all_jobs || !$no_jobs || $test_linux_x32_int64)) { + $jobs['LINUX_X32_INT64']['matrix'] = $all_variations + ? ['debug' => [true, false], 'zts' => [true, false]] + : ['debug' => [true], 'zts' => [true]]; + } if ($all_jobs || !$no_jobs || $test_macos) { $test_arm = version_compare($php_version, '8.4', '>='); $jobs['MACOS']['matrix'] = $all_variations diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index ce6053f929a0..582e88dd64fd 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -306,6 +306,93 @@ jobs: jitType: function - name: Extra tests uses: ./.github/actions/extra-tests + LINUX_X32_INT64: + if: ${{ fromJson(inputs.branch).jobs.LINUX_X32_INT64 }} + strategy: + fail-fast: false + matrix: ${{ fromJson(inputs.branch).jobs.LINUX_X32_INT64.matrix }} + name: "LINUX_X32_INT64_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}" + runs-on: ubuntu-latest + timeout-minutes: 180 + container: + image: ubuntu:${{ fromJson(inputs.branch).config.ubuntu_version }} + env: + MYSQL_TEST_HOST: mysql + PDO_MYSQL_TEST_DSN: mysql:host=mysql;dbname=test + PDO_MYSQL_TEST_HOST: mysql + PDO_FIREBIRD_TEST_DSN: firebird:dbname=firebird:test.fdb + services: + mysql: + image: mysql:8.4 + ports: + - 3306:3306 + env: + MYSQL_DATABASE: test + MYSQL_ROOT_PASSWORD: root + firebird: + image: jacobalberty/firebird + ports: + - 3050:3050 + env: + ISC_PASSWORD: test + FIREBIRD_DATABASE: test.fdb + FIREBIRD_USER: test + FIREBIRD_PASSWORD: test + steps: + - name: git checkout + uses: actions/checkout@v6 + with: + ref: ${{ fromJson(inputs.branch).ref }} + - name: apt + uses: ./.github/actions/apt-x32 + - name: ccache + uses: ./.github/actions/ccache + with: + name: "LINUX_X32_INT64_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}" + - name: System info + run: | + echo "::group::Show host CPU info" + lscpu + echo "::endgroup::" + echo "::group::Show installed package versions" + dpkg -l + echo "::endgroup::" + - name: ./configure + uses: ./.github/actions/configure-x32 + with: + # x87 computes at 80bit and feeds unrounded operands into double + # arithmetic, which diverges from every 64bit platform. SSE math + # keeps the results identical. + cflags: '-m32 -msse2 -mfpmath=sse' + configurationParameters: >- + --${{ matrix.debug && 'enable' || 'disable' }}-debug + --${{ matrix.zts && 'enable' || 'disable' }}-zts + --enable-zend-int64 + - name: make + run: make -j$(/usr/bin/nproc) >/dev/null + - name: make install + uses: ./.github/actions/install-linux-x32 + - name: Test + if: ${{ inputs.all_variations }} + uses: ./.github/actions/test-linux + - name: Test Tracing JIT + uses: ./.github/actions/test-linux + with: + enableOpcache: true + jitType: tracing + - name: Test OpCache + if: ${{ inputs.all_variations }} + uses: ./.github/actions/test-linux + with: + enableOpcache: true + - name: Test Function JIT + if: ${{ inputs.all_variations }} + uses: ./.github/actions/test-linux + with: + enableOpcache: true + jitType: function + - name: Extra tests + uses: ./.github/actions/extra-tests MACOS: if: ${{ fromJson(inputs.branch).jobs.MACOS }} strategy: From a6993759f31e2cc77d008d35e3a7693e531ea691 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Sat, 12 Sep 2026 10:43:14 +0200 Subject: [PATCH 21/23] Fix ext/dom The synthetic namespace bookkeeping keys a HashTable by the xmlNode it belongs to and casts the key back to a pointer when unlinking. Both directions went through a plain cast, which is a narrowing conversion where zend_long is wider than a pointer. Use ZEND_PTR_TO_ZEND_ULONG() and ZEND_ULONG_TO_PTR() instead. The low bit of the key stays available as the marker for the default namespace entry, as xmlNode is aligned well beyond two bytes. --- ext/dom/namespace_compat.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/dom/namespace_compat.c b/ext/dom/namespace_compat.c index 29a0714c62fc..643f5452f1d7 100644 --- a/ext/dom/namespace_compat.c +++ b/ext/dom/namespace_compat.c @@ -501,7 +501,7 @@ static xmlNsPtr dom_alloc_ns_decl(HashTable *links, xmlNodePtr node) return NULL; } - zval *zv = zend_hash_index_lookup(links, (zend_ulong) node); + zval *zv = zend_hash_index_lookup(links, ZEND_PTR_TO_ZEND_ULONG(node)); if (Z_ISNULL_P(zv)) { ZVAL_LONG(zv, 1); } else { @@ -575,7 +575,7 @@ static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node) if (node->ns && !node->ns->prefix) { /* Workaround for the behaviour where the xmlSearchNs() call inside c14n.c * can return the current namespace. */ - zend_hash_index_add_new_ptr(links, (zend_ulong) node | 1, node->ns); + zend_hash_index_add_new_ptr(links, ZEND_PTR_TO_ZEND_ULONG(node) | 1, node->ns); node->ns = xmlSearchNs(node->doc, node, NULL); } else if (node->ns) { dom_add_synthetic_ns_decl(links, node, node->ns); @@ -605,10 +605,10 @@ void dom_unlink_ns_decls(HashTable *links) { ZEND_HASH_MAP_FOREACH_NUM_KEY_VAL(links, zend_ulong h, zval *data) { if (h & 1) { - xmlNodePtr node = (xmlNodePtr) (h ^ 1); + xmlNodePtr node = (xmlNodePtr) ZEND_ULONG_TO_PTR(h ^ 1); node->ns = Z_PTR_P(data); } else { - xmlNodePtr node = (xmlNodePtr) h; + xmlNodePtr node = (xmlNodePtr) ZEND_ULONG_TO_PTR(h); while (Z_LVAL_P(data)-- > 0) { xmlNsPtr ns = node->nsDef; node->nsDef = ns->next; From 5dea370d86a1d7b904c23532adbf22868df26091 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Sat, 12 Sep 2026 21:36:40 +0200 Subject: [PATCH 22/23] Fix ext/pcntl The signal table keeps handlers in the zend_long payload of a zval while SIG_DFL, SIG_IGN and SIG_ERR are pointer constants. Converting between the two with a plain cast narrows where zend_long is wider than a pointer, and would turn SIG_ERR from -1 into its zero extended image. Use ZEND_PTR_TO_ZEND_LONG() and ZEND_LONG_TO_PTR() for those conversions and for the fault address in the siginfo array. LONG_CONST() did the same job for the constant registration and is replaced by it. php_signal() takes the SA_SIGINFO form of a handler, so handing it SIG_DFL still needs a detour over a data pointer to stay clear of -Wcast-function-type, but that one no longer passes through an integer. --- ext/pcntl/pcntl.c | 14 ++--- ext/pcntl/pcntl.stub.php | 114 +++++++++++++++++------------------ ext/pcntl/pcntl_arginfo.h | 122 +++++++++++++++++++------------------- ext/pcntl/pcntl_decl.h | 8 +-- 4 files changed, 128 insertions(+), 130 deletions(-) diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c index 265b47e52dc7..da0631a9af89 100644 --- a/ext/pcntl/pcntl.c +++ b/ext/pcntl/pcntl.c @@ -146,8 +146,6 @@ typedef psetid_t cpu_set_t; # define NSIG 32 #endif -#define LONG_CONST(c) (zend_long) c - #include "Zend/zend_enum.h" #include "Zend/zend_max_execution_timer.h" @@ -234,8 +232,8 @@ PHP_RSHUTDOWN_FUNCTION(pcntl) /* Reset all signals to their default disposition */ ZEND_HASH_FOREACH_NUM_KEY_VAL(&PCNTL_G(php_signal_table), signo, handle) { - if (Z_TYPE_P(handle) != IS_LONG || Z_LVAL_P(handle) != (zend_long)SIG_DFL) { - php_signal(signo, (Sigfunc *)(zend_long)SIG_DFL, false); + if (Z_TYPE_P(handle) != IS_LONG || Z_LVAL_P(handle) != ZEND_PTR_TO_ZEND_LONG(SIG_DFL)) { + php_signal(signo, (Sigfunc *) (void *) SIG_DFL, false); } } ZEND_HASH_FOREACH_END(); @@ -827,11 +825,11 @@ PHP_FUNCTION(pcntl_signal) /* Special long value case for SIG_DFL and SIG_IGN */ if (Z_TYPE_P(handle) == IS_LONG) { - if (Z_LVAL_P(handle) != (zend_long) SIG_DFL && Z_LVAL_P(handle) != (zend_long) SIG_IGN) { + if (Z_LVAL_P(handle) != ZEND_PTR_TO_ZEND_LONG(SIG_DFL) && Z_LVAL_P(handle) != ZEND_PTR_TO_ZEND_LONG(SIG_IGN)) { zend_argument_value_error(2, "must be either SIG_DFL or SIG_IGN when an integer value is given"); RETURN_THROWS(); } - if (php_signal(signo, (Sigfunc *) Z_LVAL_P(handle), restart_syscalls) == (void *)SIG_ERR) { + if (php_signal(signo, (Sigfunc *) ZEND_LONG_TO_PTR(Z_LVAL_P(handle)), restart_syscalls) == (void *)SIG_ERR) { PCNTL_G(last_error) = errno; php_error_docref(NULL, E_WARNING, "Error assigning signal"); RETURN_FALSE; @@ -879,7 +877,7 @@ PHP_FUNCTION(pcntl_signal_get_handler) if ((prev_handle = zend_hash_index_find(&PCNTL_G(php_signal_table), signo)) != NULL) { RETURN_COPY(prev_handle); } else { - RETURN_LONG((zend_long)SIG_DFL); + RETURN_LONG(ZEND_PTR_TO_ZEND_LONG(SIG_DFL)); } } @@ -1145,7 +1143,7 @@ static void pcntl_siginfo_to_zval(int signo, siginfo_t *siginfo, zval *user_sigi case SIGFPE: case SIGSEGV: case SIGBUS: - add_assoc_long_ex(user_siginfo, "addr", sizeof("addr")-1, (zend_long)siginfo->si_addr); + add_assoc_long_ex(user_siginfo, "addr", sizeof("addr")-1, ZEND_PTR_TO_ZEND_LONG(siginfo->si_addr)); break; #if defined(SIGPOLL) && !defined(__CYGWIN__) case SIGPOLL: diff --git a/ext/pcntl/pcntl.stub.php b/ext/pcntl/pcntl.stub.php index 1ab52d025036..8b5b2432e0d3 100644 --- a/ext/pcntl/pcntl.stub.php +++ b/ext/pcntl/pcntl.stub.php @@ -12,42 +12,42 @@ #ifdef WNOHANG /** * @var int - * @cvalue LONG_CONST(WNOHANG) + * @cvalue ZEND_PTR_TO_ZEND_LONG(WNOHANG) */ const WNOHANG = UNKNOWN; #endif #ifdef WUNTRACED /** * @var int - * @cvalue LONG_CONST(WUNTRACED) + * @cvalue ZEND_PTR_TO_ZEND_LONG(WUNTRACED) */ const WUNTRACED = UNKNOWN; #endif #ifdef HAVE_WCONTINUED /** * @var int - * @cvalue LONG_CONST(WCONTINUED) + * @cvalue ZEND_PTR_TO_ZEND_LONG(WCONTINUED) */ const WCONTINUED = UNKNOWN; #endif #if defined (HAVE_DECL_WEXITED) && HAVE_DECL_WEXITED == 1 /** * @var int - * @cvalue LONG_CONST(WEXITED) + * @cvalue ZEND_PTR_TO_ZEND_LONG(WEXITED) */ const WEXITED = UNKNOWN; #endif #if defined (HAVE_DECL_WSTOPPED) && HAVE_DECL_WSTOPPED == 1 /** * @var int - * @cvalue LONG_CONST(WSTOPPED) + * @cvalue ZEND_PTR_TO_ZEND_LONG(WSTOPPED) */ const WSTOPPED = UNKNOWN; #endif #if defined (HAVE_DECL_WNOWAIT) && HAVE_DECL_WNOWAIT== 1 /** * @var int - * @cvalue LONG_CONST(WNOWAIT) + * @cvalue ZEND_PTR_TO_ZEND_LONG(WNOWAIT) */ const WNOWAIT = UNKNOWN; #endif @@ -57,17 +57,17 @@ #ifdef HAVE_POSIX_IDTYPES /** * @var int - * @cvalue LONG_CONST(P_ALL) + * @cvalue ZEND_PTR_TO_ZEND_LONG(P_ALL) */ const P_ALL = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(P_PID) + * @cvalue ZEND_PTR_TO_ZEND_LONG(P_PID) */ const P_PID = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(P_PGID) + * @cvalue ZEND_PTR_TO_ZEND_LONG(P_PGID) */ const P_PGID = UNKNOWN; #endif @@ -75,7 +75,7 @@ #ifdef HAVE_LINUX_IDTYPES /** * @var int - * @cvalue LONG_CONST(P_PIDFD) + * @cvalue ZEND_PTR_TO_ZEND_LONG(P_PIDFD) */ const P_PIDFD = UNKNOWN; #endif @@ -83,17 +83,17 @@ #ifdef HAVE_NETBSD_IDTYPES /** * @var int - * @cvalue LONG_CONST(P_UID) + * @cvalue ZEND_PTR_TO_ZEND_LONG(P_UID) */ const P_UID = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(P_GID) + * @cvalue ZEND_PTR_TO_ZEND_LONG(P_GID) */ const P_GID = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(P_SID) + * @cvalue ZEND_PTR_TO_ZEND_LONG(P_SID) */ const P_SID = UNKNOWN; #endif @@ -101,7 +101,7 @@ #ifdef HAVE_FREEBSD_IDTYPES /** * @var int - * @cvalue LONG_CONST(P_JAILID) + * @cvalue ZEND_PTR_TO_ZEND_LONG(P_JAILID) */ const P_JAILID = UNKNOWN; #endif @@ -111,244 +111,244 @@ /** * @var int - * @cvalue LONG_CONST(SIG_IGN) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIG_IGN) */ const SIG_IGN = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIG_DFL) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIG_DFL) */ const SIG_DFL = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIG_ERR) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIG_ERR) */ const SIG_ERR = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGHUP) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGHUP) */ const SIGHUP = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGINT) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGINT) */ const SIGINT = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGQUIT) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGQUIT) */ const SIGQUIT = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGILL) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGILL) */ const SIGILL = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGTRAP) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGTRAP) */ const SIGTRAP = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGABRT) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGABRT) */ const SIGABRT = UNKNOWN; #ifdef SIGIOT /** * @var int - * @cvalue LONG_CONST(SIGIOT) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGIOT) */ const SIGIOT = UNKNOWN; #endif /** * @var int - * @cvalue LONG_CONST(SIGBUS) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGBUS) */ const SIGBUS = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGFPE) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGFPE) */ const SIGFPE = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGKILL) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGKILL) */ const SIGKILL = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGUSR1) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGUSR1) */ const SIGUSR1 = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGSEGV) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGSEGV) */ const SIGSEGV = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGUSR2) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGUSR2) */ const SIGUSR2 = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGPIPE) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGPIPE) */ const SIGPIPE = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGALRM) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGALRM) */ const SIGALRM = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGTERM) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGTERM) */ const SIGTERM = UNKNOWN; #ifdef SIGSTKFLT /** * @var int - * @cvalue LONG_CONST(SIGSTKFLT) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGSTKFLT) */ const SIGSTKFLT = UNKNOWN; #endif #ifdef SIGCLD /** * @var int - * @cvalue LONG_CONST(SIGCLD) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGCLD) */ const SIGCLD = UNKNOWN; #endif #ifdef SIGCHLD /** * @var int - * @cvalue LONG_CONST(SIGCHLD) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGCHLD) */ const SIGCHLD = UNKNOWN; #endif /** * @var int - * @cvalue LONG_CONST(SIGCONT) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGCONT) */ const SIGCONT = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGSTOP) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGSTOP) */ const SIGSTOP = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGTSTP) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGTSTP) */ const SIGTSTP = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGTTIN) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGTTIN) */ const SIGTTIN = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGTTOU) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGTTOU) */ const SIGTTOU = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGURG) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGURG) */ const SIGURG = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGXCPU) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGXCPU) */ const SIGXCPU = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGXFSZ) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGXFSZ) */ const SIGXFSZ = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGVTALRM) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGVTALRM) */ const SIGVTALRM = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGPROF) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGPROF) */ const SIGPROF = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGWINCH) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGWINCH) */ const SIGWINCH = UNKNOWN; #ifdef SIGPOLL /** * @var int - * @cvalue LONG_CONST(SIGPOLL) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGPOLL) */ const SIGPOLL = UNKNOWN; #endif #ifdef SIGIO /** * @var int - * @cvalue LONG_CONST(SIGIO) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGIO) */ const SIGIO = UNKNOWN; #endif #ifdef SIGPWR /** * @var int - * @cvalue LONG_CONST(SIGPWR) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGPWR) */ const SIGPWR = UNKNOWN; #endif #ifdef SIGINFO /** * @var int - * @cvalue LONG_CONST(SIGINFO) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGINFO) */ const SIGINFO = UNKNOWN; #endif #ifdef SIGSYS /** * @var int - * @cvalue LONG_CONST(SIGSYS) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGSYS) */ const SIGSYS = UNKNOWN; /** * @var int - * @cvalue LONG_CONST(SIGSYS) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGSYS) */ const SIGBABY = UNKNOWN; #endif #ifdef SIGCKPT /** * @var int - * @cvalue LONG_CONST(SIGCKPT) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGCKPT) */ const SIGCKPT = UNKNOWN; #endif #ifdef SIGCKPTEXIT /** * @var int - * @cvalue LONG_CONST(SIGCKPTEXIT) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGCKPTEXIT) */ const SIGCKPTEXIT = UNKNOWN; #endif #ifdef SIGRTMIN /** * @var int - * @cvalue LONG_CONST(SIGRTMIN) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGRTMIN) */ const SIGRTMIN = UNKNOWN; #endif #ifdef SIGRTMAX /** * @var int - * @cvalue LONG_CONST(SIGRTMAX) + * @cvalue ZEND_PTR_TO_ZEND_LONG(SIGRTMAX) */ const SIGRTMAX = UNKNOWN; #endif diff --git a/ext/pcntl/pcntl_arginfo.h b/ext/pcntl/pcntl_arginfo.h index a9d6bb010e29..22ea951efbf0 100644 --- a/ext/pcntl/pcntl_arginfo.h +++ b/ext/pcntl/pcntl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit pcntl.stub.php instead. - * Stub hash: ec6306e93fad6d127ff880fc01736ac287619cf7 + * Stub hash: 069a83d911b2cea1f5d208cb40ff355ed5a361e2 * Has decl header: yes */ #include "zend_constants.h" @@ -303,107 +303,107 @@ static const zend_function_entry ext_functions[] = { static void register_pcntl_symbols(int module_number) { #if defined(WNOHANG) - REGISTER_LONG_CONSTANT("WNOHANG", LONG_CONST(WNOHANG), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("WNOHANG", ZEND_PTR_TO_ZEND_LONG(WNOHANG), CONST_PERSISTENT); #endif #if defined(WUNTRACED) - REGISTER_LONG_CONSTANT("WUNTRACED", LONG_CONST(WUNTRACED), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("WUNTRACED", ZEND_PTR_TO_ZEND_LONG(WUNTRACED), CONST_PERSISTENT); #endif #if defined(HAVE_WCONTINUED) - REGISTER_LONG_CONSTANT("WCONTINUED", LONG_CONST(WCONTINUED), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("WCONTINUED", ZEND_PTR_TO_ZEND_LONG(WCONTINUED), CONST_PERSISTENT); #endif #if defined (HAVE_DECL_WEXITED) && HAVE_DECL_WEXITED == 1 - REGISTER_LONG_CONSTANT("WEXITED", LONG_CONST(WEXITED), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("WEXITED", ZEND_PTR_TO_ZEND_LONG(WEXITED), CONST_PERSISTENT); #endif #if defined (HAVE_DECL_WSTOPPED) && HAVE_DECL_WSTOPPED == 1 - REGISTER_LONG_CONSTANT("WSTOPPED", LONG_CONST(WSTOPPED), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("WSTOPPED", ZEND_PTR_TO_ZEND_LONG(WSTOPPED), CONST_PERSISTENT); #endif #if defined (HAVE_DECL_WNOWAIT) && HAVE_DECL_WNOWAIT== 1 - REGISTER_LONG_CONSTANT("WNOWAIT", LONG_CONST(WNOWAIT), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("WNOWAIT", ZEND_PTR_TO_ZEND_LONG(WNOWAIT), CONST_PERSISTENT); #endif #if defined(HAVE_WAITID) && defined(HAVE_POSIX_IDTYPES) - REGISTER_LONG_CONSTANT("P_ALL", LONG_CONST(P_ALL), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("P_PID", LONG_CONST(P_PID), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("P_PGID", LONG_CONST(P_PGID), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("P_ALL", ZEND_PTR_TO_ZEND_LONG(P_ALL), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("P_PID", ZEND_PTR_TO_ZEND_LONG(P_PID), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("P_PGID", ZEND_PTR_TO_ZEND_LONG(P_PGID), CONST_PERSISTENT); #endif #if defined(HAVE_WAITID) && defined(HAVE_LINUX_IDTYPES) - REGISTER_LONG_CONSTANT("P_PIDFD", LONG_CONST(P_PIDFD), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("P_PIDFD", ZEND_PTR_TO_ZEND_LONG(P_PIDFD), CONST_PERSISTENT); #endif #if defined(HAVE_WAITID) && defined(HAVE_NETBSD_IDTYPES) - REGISTER_LONG_CONSTANT("P_UID", LONG_CONST(P_UID), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("P_GID", LONG_CONST(P_GID), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("P_SID", LONG_CONST(P_SID), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("P_UID", ZEND_PTR_TO_ZEND_LONG(P_UID), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("P_GID", ZEND_PTR_TO_ZEND_LONG(P_GID), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("P_SID", ZEND_PTR_TO_ZEND_LONG(P_SID), CONST_PERSISTENT); #endif #if defined(HAVE_WAITID) && defined(HAVE_FREEBSD_IDTYPES) - REGISTER_LONG_CONSTANT("P_JAILID", LONG_CONST(P_JAILID), CONST_PERSISTENT); -#endif - REGISTER_LONG_CONSTANT("SIG_IGN", LONG_CONST(SIG_IGN), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIG_DFL", LONG_CONST(SIG_DFL), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIG_ERR", LONG_CONST(SIG_ERR), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGHUP", LONG_CONST(SIGHUP), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGINT", LONG_CONST(SIGINT), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGQUIT", LONG_CONST(SIGQUIT), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGILL", LONG_CONST(SIGILL), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGTRAP", LONG_CONST(SIGTRAP), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGABRT", LONG_CONST(SIGABRT), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("P_JAILID", ZEND_PTR_TO_ZEND_LONG(P_JAILID), CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("SIG_IGN", ZEND_PTR_TO_ZEND_LONG(SIG_IGN), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIG_DFL", ZEND_PTR_TO_ZEND_LONG(SIG_DFL), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIG_ERR", ZEND_PTR_TO_ZEND_LONG(SIG_ERR), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGHUP", ZEND_PTR_TO_ZEND_LONG(SIGHUP), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGINT", ZEND_PTR_TO_ZEND_LONG(SIGINT), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGQUIT", ZEND_PTR_TO_ZEND_LONG(SIGQUIT), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGILL", ZEND_PTR_TO_ZEND_LONG(SIGILL), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGTRAP", ZEND_PTR_TO_ZEND_LONG(SIGTRAP), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGABRT", ZEND_PTR_TO_ZEND_LONG(SIGABRT), CONST_PERSISTENT); #if defined(SIGIOT) - REGISTER_LONG_CONSTANT("SIGIOT", LONG_CONST(SIGIOT), CONST_PERSISTENT); -#endif - REGISTER_LONG_CONSTANT("SIGBUS", LONG_CONST(SIGBUS), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGFPE", LONG_CONST(SIGFPE), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGKILL", LONG_CONST(SIGKILL), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGUSR1", LONG_CONST(SIGUSR1), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGSEGV", LONG_CONST(SIGSEGV), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGUSR2", LONG_CONST(SIGUSR2), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGPIPE", LONG_CONST(SIGPIPE), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGALRM", LONG_CONST(SIGALRM), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGTERM", LONG_CONST(SIGTERM), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGIOT", ZEND_PTR_TO_ZEND_LONG(SIGIOT), CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("SIGBUS", ZEND_PTR_TO_ZEND_LONG(SIGBUS), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGFPE", ZEND_PTR_TO_ZEND_LONG(SIGFPE), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGKILL", ZEND_PTR_TO_ZEND_LONG(SIGKILL), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGUSR1", ZEND_PTR_TO_ZEND_LONG(SIGUSR1), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGSEGV", ZEND_PTR_TO_ZEND_LONG(SIGSEGV), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGUSR2", ZEND_PTR_TO_ZEND_LONG(SIGUSR2), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGPIPE", ZEND_PTR_TO_ZEND_LONG(SIGPIPE), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGALRM", ZEND_PTR_TO_ZEND_LONG(SIGALRM), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGTERM", ZEND_PTR_TO_ZEND_LONG(SIGTERM), CONST_PERSISTENT); #if defined(SIGSTKFLT) - REGISTER_LONG_CONSTANT("SIGSTKFLT", LONG_CONST(SIGSTKFLT), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGSTKFLT", ZEND_PTR_TO_ZEND_LONG(SIGSTKFLT), CONST_PERSISTENT); #endif #if defined(SIGCLD) - REGISTER_LONG_CONSTANT("SIGCLD", LONG_CONST(SIGCLD), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGCLD", ZEND_PTR_TO_ZEND_LONG(SIGCLD), CONST_PERSISTENT); #endif #if defined(SIGCHLD) - REGISTER_LONG_CONSTANT("SIGCHLD", LONG_CONST(SIGCHLD), CONST_PERSISTENT); -#endif - REGISTER_LONG_CONSTANT("SIGCONT", LONG_CONST(SIGCONT), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGSTOP", LONG_CONST(SIGSTOP), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGTSTP", LONG_CONST(SIGTSTP), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGTTIN", LONG_CONST(SIGTTIN), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGTTOU", LONG_CONST(SIGTTOU), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGURG", LONG_CONST(SIGURG), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGXCPU", LONG_CONST(SIGXCPU), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGXFSZ", LONG_CONST(SIGXFSZ), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGVTALRM", LONG_CONST(SIGVTALRM), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGPROF", LONG_CONST(SIGPROF), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGWINCH", LONG_CONST(SIGWINCH), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGCHLD", ZEND_PTR_TO_ZEND_LONG(SIGCHLD), CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("SIGCONT", ZEND_PTR_TO_ZEND_LONG(SIGCONT), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGSTOP", ZEND_PTR_TO_ZEND_LONG(SIGSTOP), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGTSTP", ZEND_PTR_TO_ZEND_LONG(SIGTSTP), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGTTIN", ZEND_PTR_TO_ZEND_LONG(SIGTTIN), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGTTOU", ZEND_PTR_TO_ZEND_LONG(SIGTTOU), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGURG", ZEND_PTR_TO_ZEND_LONG(SIGURG), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGXCPU", ZEND_PTR_TO_ZEND_LONG(SIGXCPU), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGXFSZ", ZEND_PTR_TO_ZEND_LONG(SIGXFSZ), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGVTALRM", ZEND_PTR_TO_ZEND_LONG(SIGVTALRM), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGPROF", ZEND_PTR_TO_ZEND_LONG(SIGPROF), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGWINCH", ZEND_PTR_TO_ZEND_LONG(SIGWINCH), CONST_PERSISTENT); #if defined(SIGPOLL) - REGISTER_LONG_CONSTANT("SIGPOLL", LONG_CONST(SIGPOLL), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGPOLL", ZEND_PTR_TO_ZEND_LONG(SIGPOLL), CONST_PERSISTENT); #endif #if defined(SIGIO) - REGISTER_LONG_CONSTANT("SIGIO", LONG_CONST(SIGIO), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGIO", ZEND_PTR_TO_ZEND_LONG(SIGIO), CONST_PERSISTENT); #endif #if defined(SIGPWR) - REGISTER_LONG_CONSTANT("SIGPWR", LONG_CONST(SIGPWR), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGPWR", ZEND_PTR_TO_ZEND_LONG(SIGPWR), CONST_PERSISTENT); #endif #if defined(SIGINFO) - REGISTER_LONG_CONSTANT("SIGINFO", LONG_CONST(SIGINFO), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGINFO", ZEND_PTR_TO_ZEND_LONG(SIGINFO), CONST_PERSISTENT); #endif #if defined(SIGSYS) - REGISTER_LONG_CONSTANT("SIGSYS", LONG_CONST(SIGSYS), CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGBABY", LONG_CONST(SIGSYS), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGSYS", ZEND_PTR_TO_ZEND_LONG(SIGSYS), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGBABY", ZEND_PTR_TO_ZEND_LONG(SIGSYS), CONST_PERSISTENT); #endif #if defined(SIGCKPT) - REGISTER_LONG_CONSTANT("SIGCKPT", LONG_CONST(SIGCKPT), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGCKPT", ZEND_PTR_TO_ZEND_LONG(SIGCKPT), CONST_PERSISTENT); #endif #if defined(SIGCKPTEXIT) - REGISTER_LONG_CONSTANT("SIGCKPTEXIT", LONG_CONST(SIGCKPTEXIT), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGCKPTEXIT", ZEND_PTR_TO_ZEND_LONG(SIGCKPTEXIT), CONST_PERSISTENT); #endif #if defined(SIGRTMIN) - REGISTER_LONG_CONSTANT("SIGRTMIN", LONG_CONST(SIGRTMIN), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGRTMIN", ZEND_PTR_TO_ZEND_LONG(SIGRTMIN), CONST_PERSISTENT); #endif #if defined(SIGRTMAX) - REGISTER_LONG_CONSTANT("SIGRTMAX", LONG_CONST(SIGRTMAX), CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGRTMAX", ZEND_PTR_TO_ZEND_LONG(SIGRTMAX), CONST_PERSISTENT); #endif #if (defined(HAVE_GETPRIORITY) || defined(HAVE_SETPRIORITY)) REGISTER_LONG_CONSTANT("PRIO_PGRP", PRIO_PGRP, CONST_PERSISTENT); diff --git a/ext/pcntl/pcntl_decl.h b/ext/pcntl/pcntl_decl.h index a0fdb4dde755..1e23c4e1b355 100644 --- a/ext/pcntl/pcntl_decl.h +++ b/ext/pcntl/pcntl_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit pcntl.stub.php instead. - * Stub hash: ec6306e93fad6d127ff880fc01736ac287619cf7 */ + * Stub hash: 069a83d911b2cea1f5d208cb40ff355ed5a361e2 */ -#ifndef ZEND_PCNTL_DECL_ec6306e93fad6d127ff880fc01736ac287619cf7_H -#define ZEND_PCNTL_DECL_ec6306e93fad6d127ff880fc01736ac287619cf7_H +#ifndef ZEND_PCNTL_DECL_069a83d911b2cea1f5d208cb40ff355ed5a361e2_H +#define ZEND_PCNTL_DECL_069a83d911b2cea1f5d208cb40ff355ed5a361e2_H typedef enum zend_enum_Pcntl_QosClass { ZEND_ENUM_Pcntl_QosClass_UserInteractive = 1, @@ -12,4 +12,4 @@ typedef enum zend_enum_Pcntl_QosClass { ZEND_ENUM_Pcntl_QosClass_Background = 5, } zend_enum_Pcntl_QosClass; -#endif /* ZEND_PCNTL_DECL_ec6306e93fad6d127ff880fc01736ac287619cf7_H */ +#endif /* ZEND_PCNTL_DECL_069a83d911b2cea1f5d208cb40ff355ed5a361e2_H */ From 55c537c526adc8abcc9bfa57b4cd30ddab5f1778 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Sun, 13 Sep 2026 12:04:43 +0200 Subject: [PATCH 23/23] Fix ext/mysqlnd The i386 variant of the wire format accessors types the 4 byte cases as zend_long, which the imported code spelled long and which is exactly 32 bits on that platform. With ZEND_INT64 it is 64 bits, so sint4korr() and uint4korr() read 8 bytes instead of 4, int4store() writes 8 bytes into a 4 byte destination, and float8get_union covers 16 bytes rather than the 8 of the double it aliases. COM_STMT_CLOSE and COM_STMT_RESET build their payload with int4store() in a 4 byte stack buffer, so the write runs off the end of it. Type the four accessors int32_t and uint32_t. The 2 and 8 byte siblings in the same block, and the generic definitions used on every other platform, already spell them that way. --- ext/mysqlnd/mysqlnd_portability.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/mysqlnd/mysqlnd_portability.h b/ext/mysqlnd/mysqlnd_portability.h index 20dfe3429edf..b41ccf355af8 100644 --- a/ext/mysqlnd/mysqlnd_portability.h +++ b/ext/mysqlnd/mysqlnd_portability.h @@ -118,13 +118,13 @@ This file is public domain and comes with NO WARRANTY of any kind */ (((uint32_t) (zend_uchar) (A)[2]) << 16) |\ (((uint32_t) (zend_uchar) (A)[1]) << 8) | \ ((uint32_t) (zend_uchar) (A)[0]))) -#define sint4korr(A) (*((zend_long *) (A))) +#define sint4korr(A) (*((int32_t *) (A))) #define uint2korr(A) (*((uint16_t *) (A))) #define uint3korr(A) (uint32_t) (((uint32_t) ((zend_uchar) (A)[0])) +\ (((uint32_t) ((zend_uchar) (A)[1])) << 8) +\ (((uint32_t) ((zend_uchar) (A)[2])) << 16)) -#define uint4korr(A) (*((zend_ulong *) (A))) +#define uint4korr(A) (*((uint32_t *) (A))) @@ -135,7 +135,7 @@ This file is public domain and comes with NO WARRANTY of any kind */ *(T)= (zend_uchar) ((A));\ *(T+1)=(zend_uchar) (((uint32_t) (A) >> 8));\ *(T+2)=(zend_uchar) (((A) >> 16)); } -#define int4store(T,A) *((zend_long *) (T))= (zend_long) (A) +#define int4store(T,A) *((uint32_t *) (T))= (uint32_t) (A) #define int5store(T,A) { \ *((zend_uchar *)(T))= (zend_uchar)((A));\ *(((zend_uchar *)(T))+1)=(zend_uchar) (((A) >> 8));\ @@ -147,12 +147,12 @@ This file is public domain and comes with NO WARRANTY of any kind */ typedef union { double v; - zend_long m[2]; + int32_t m[2]; } float8get_union; -#define float8get(V,M) { ((float8get_union *)&(V))->m[0] = *((zend_long*) (M)); \ - ((float8get_union *)&(V))->m[1] = *(((zend_long*) (M))+1); } -#define float8store(T,V) { *((zend_long *) (T)) = ((float8get_union *)&(V))->m[0]; \ - *(((zend_long *) (T))+1) = ((float8get_union *)&(V))->m[1]; } +#define float8get(V,M) { ((float8get_union *)&(V))->m[0] = *((int32_t*) (M)); \ + ((float8get_union *)&(V))->m[1] = *(((int32_t*) (M))+1); } +#define float8store(T,V) { *((int32_t *) (T)) = ((float8get_union *)&(V))->m[0]; \ + *(((int32_t *) (T))+1) = ((float8get_union *)&(V))->m[1]; } #define float4get(V,M) { *((float *) &(V)) = *((float*) (M)); } /* From Andrey Hristov based on float8get */ #define floatget(V,M) memcpy((char*) &(V),(char*) (M),sizeof(float))