From f785614f5164ee21128a47760d16bdb377052cc6 Mon Sep 17 00:00:00 2001 From: henderkes Date: Fri, 12 Jun 2026 16:03:43 +0700 Subject: [PATCH 1/2] TSRM: make CG, EG, SCNG and AG compile-time offsets Saves an offset load every time they're accessed Closes GH-22287 --- NEWS | 1 + TSRM/TSRM.c | 57 +++++++++++++++++++++++++++----------- TSRM/TSRM.h | 6 ++++ Zend/zend.c | 9 ++++-- Zend/zend_alloc.c | 6 ++-- Zend/zend_globals.h | 8 ++++++ Zend/zend_globals_macros.h | 6 ++-- main/main.c | 6 ++++ 8 files changed, 75 insertions(+), 24 deletions(-) diff --git a/NEWS b/NEWS index a4e0e3ba4f03..91e7169f98fd 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,7 @@ PHP NEWS . perf: make all static extensions use TSRMG_STATIC. (henderkes) . Fixed bug GH-22257 (type confusion in Exception::getTraceAsString()). (David Carlier) + . TSRM: make CG, EG, SCNG and AG compile-time offsets. (henderkes) - BCMath: . Added NUL-byte validation to BCMath functions. (jorgsowa) diff --git a/TSRM/TSRM.c b/TSRM/TSRM.c index e99993204b6f..4222e88755d6 100644 --- a/TSRM/TSRM.c +++ b/TSRM/TSRM.c @@ -36,12 +36,11 @@ struct _tsrm_tls_entry { tsrm_tls_entry *next; }; - typedef struct { size_t size; ts_allocate_ctor ctor; ts_allocate_dtor dtor; - size_t fast_offset; + ptrdiff_t fast_offset; int done; } tsrm_resource_type; @@ -58,6 +57,7 @@ static int resource_types_table_size; /* Reserved space for fast globals access */ static size_t tsrm_reserved_pos = 0; static size_t tsrm_reserved_size = 0; +static size_t tsrm_reserved_front = 0; static MUTEX_T tsmm_mutex; /* thread-safe memory manager mutex */ static MUTEX_T tsrm_env_mutex; /* tsrm environ mutex */ @@ -155,6 +155,7 @@ TSRM_API bool tsrm_startup(int expected_threads, int expected_resources, int deb tsrm_reserved_pos = 0; tsrm_reserved_size = 0; + tsrm_reserved_front = 0; tsrm_env_mutex = tsrm_mutex_alloc(); @@ -205,7 +206,7 @@ TSRM_API void tsrm_shutdown(void) } else { free(p->storage); } - free(p); + free((char *) p - tsrm_reserved_front); p = next_p; } } @@ -232,6 +233,7 @@ TSRM_API void tsrm_shutdown(void) tsrm_reserved_pos = 0; tsrm_reserved_size = 0; + tsrm_reserved_front = 0; }/*}}}*/ /* {{{ */ @@ -319,17 +321,20 @@ TSRM_API void tsrm_reserve(size_t size) }/*}}}*/ +/* Carve a fixed-offset front region out of the reserved space. It is placed + * before the TLS entry, so the hot globals get compile-time-constant negative + * offsets from the cache pointer. */ +TSRM_API void tsrm_reserve_fast_front(size_t size) +{ + tsrm_reserved_front = TSRM_ALIGNED_SIZE(size); + tsrm_reserved_size -= tsrm_reserved_front; +} + + /* allocates a new fast thread-safe-resource id */ TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor) {/*{{{*/ - TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtaining a new fast resource id, %d bytes", size)); - tsrm_mutex_lock(tsmm_mutex); - - /* obtain a resource id */ - *rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++); - TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtained resource id %d", *rsrc_id)); - size = TSRM_ALIGNED_SIZE(size); if (tsrm_reserved_size - tsrm_reserved_pos < size) { TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate space for fast resource")); @@ -338,9 +343,26 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, siz tsrm_mutex_unlock(tsmm_mutex); return 0; } - - *offset = TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_pos; + ptrdiff_t fixed_offset = TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_pos; tsrm_reserved_pos += size; + tsrm_mutex_unlock(tsmm_mutex); + + return ts_allocate_fast_id_at(rsrc_id, offset, fixed_offset, size, ctor, dtor); +}/*}}}*/ + + +TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, ptrdiff_t fixed_offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor) +{ + TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtaining a new fast resource id, %d bytes", size)); + + tsrm_mutex_lock(tsmm_mutex); + + /* obtain a resource id */ + *rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++); + TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtained resource id %d", *rsrc_id)); + + size = TSRM_ALIGNED_SIZE(size); + *offset = (size_t) fixed_offset; /* store the new resource type in the resource sizes table */ if (resource_types_table_size < id_count) { @@ -366,7 +388,7 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, siz TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Successfully allocated new resource id %d", *rsrc_id)); return *rsrc_id; -}/*}}}*/ +} static void set_thread_local_storage_resource_to(tsrm_tls_entry *thread_resource) { @@ -378,7 +400,10 @@ static void set_thread_local_storage_resource_to(tsrm_tls_entry *thread_resource static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_T thread_id) {/*{{{*/ TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Creating data structures for thread %x", thread_id)); - (*thread_resources_ptr) = (tsrm_tls_entry *) malloc(TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_size); + /* The entry follows the fixed-offset front region. + * hot globals live at negative offsets from the TLS cache pointer. */ + char *block = (char *) malloc(tsrm_reserved_front + TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_size); + (*thread_resources_ptr) = (tsrm_tls_entry *) (block + tsrm_reserved_front); (*thread_resources_ptr)->storage = NULL; if (id_count > 0) { (*thread_resources_ptr)->storage = (void **) malloc(sizeof(void *)*id_count); @@ -487,7 +512,7 @@ TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id) set_thread_local_storage_resource_to(thread_resources); /* Free up the old resource from the old thread instance */ ts_free_resources(thread_resources); - free(thread_resources); + free((char *) thread_resources - tsrm_reserved_front); /* Allocate a new resource at the same point in the linked list, and relink the next pointer */ allocate_new_resource(last_thread_resources, thread_id); thread_resources = *last_thread_resources; @@ -529,7 +554,7 @@ void ts_free_thread(void) tsrm_tls_table[hash_value] = thread_resources->next; } tsrm_tls_set(0); - free(thread_resources); + free((char *) thread_resources - tsrm_reserved_front); break; } if (thread_resources->next) { diff --git a/TSRM/TSRM.h b/TSRM/TSRM.h index 639b1134ddde..2e8cbddfcda7 100644 --- a/TSRM/TSRM.h +++ b/TSRM/TSRM.h @@ -20,6 +20,7 @@ # include
#endif +#include #include #include @@ -94,6 +95,11 @@ TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate TSRM_API void tsrm_reserve(size_t size); TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor); +/* Fast resources at caller-chosen, compile-time-constant offsets. The fixed + * front region must be reserved after tsrm_reserve() and before any fast id. */ +TSRM_API void tsrm_reserve_fast_front(size_t size); +TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, ptrdiff_t fixed_offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor); + /* fetches the requested resource for the current thread */ TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id); #define ts_resource(id) ts_resource_ex(id, NULL) diff --git a/Zend/zend.c b/Zend/zend.c index f16b1a30dbbc..9411b92a2018 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1019,9 +1019,12 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ zend_init_rsrc_list_dtors(); #ifdef ZTS - ts_allocate_fast_id(&compiler_globals_id, &compiler_globals_offset, sizeof(zend_compiler_globals), (ts_allocate_ctor) compiler_globals_ctor, (ts_allocate_dtor) compiler_globals_dtor); - ts_allocate_fast_id(&executor_globals_id, &executor_globals_offset, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor); - ts_allocate_fast_id(&language_scanner_globals_id, &language_scanner_globals_offset, sizeof(zend_php_scanner_globals), (ts_allocate_ctor) php_scanner_globals_ctor, NULL); + ts_allocate_fast_id_at(&compiler_globals_id, &compiler_globals_offset, ZEND_CG_OFFSET, sizeof(zend_compiler_globals), (ts_allocate_ctor) compiler_globals_ctor, (ts_allocate_dtor) compiler_globals_dtor); + ts_allocate_fast_id_at(&executor_globals_id, &executor_globals_offset, ZEND_EG_OFFSET, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor); + ts_allocate_fast_id_at(&language_scanner_globals_id, &language_scanner_globals_offset, ZEND_SCNG_OFFSET, sizeof(zend_php_scanner_globals), (ts_allocate_ctor) php_scanner_globals_ctor, NULL); + ZEND_ASSERT(compiler_globals_offset == ZEND_CG_OFFSET); + ZEND_ASSERT(executor_globals_offset == ZEND_EG_OFFSET); + ZEND_ASSERT(language_scanner_globals_offset == ZEND_SCNG_OFFSET); ts_allocate_fast_id(&ini_scanner_globals_id, &ini_scanner_globals_offset, sizeof(zend_ini_scanner_globals), (ts_allocate_ctor) ini_scanner_globals_ctor, NULL); compiler_globals = ts_resource(compiler_globals_id); executor_globals = ts_resource(executor_globals_id); diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 942a8b8e1309..d0f2b221b9a7 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2614,7 +2614,8 @@ typedef struct _zend_alloc_globals { #ifdef ZTS static int alloc_globals_id; static size_t alloc_globals_offset; -# define AG(v) ZEND_TSRMG_FAST(alloc_globals_offset, zend_alloc_globals *, v) +# define ZEND_AG_OFFSET (ZEND_SCNG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_alloc_globals))) +# define AG(v) ZEND_TSRMG_FAST(ZEND_AG_OFFSET, zend_alloc_globals *, v) #else # define AG(v) (alloc_globals.v) static zend_alloc_globals alloc_globals; @@ -3335,7 +3336,8 @@ ZEND_API void start_memory_manager(void) # endif #endif #ifdef ZTS - ts_allocate_fast_id(&alloc_globals_id, &alloc_globals_offset, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor); + ts_allocate_fast_id_at(&alloc_globals_id, &alloc_globals_offset, ZEND_AG_OFFSET, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor); + ZEND_ASSERT(alloc_globals_offset == ZEND_AG_OFFSET); #else alloc_globals_ctor(&alloc_globals); #endif diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 8257df32e831..61499c0cc23d 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -329,6 +329,14 @@ struct _zend_executor_globals { void *reserved[ZEND_MAX_RESERVED_RESOURCES]; }; +#ifdef ZTS +/* Compile-time offsets of the hot globals, in a reserved region just before the + * cache pointer. ZEND_AG_OFFSET is furthest, in zend_alloc.c. */ +# define ZEND_CG_OFFSET (-(ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_compiler_globals))) +# define ZEND_EG_OFFSET (ZEND_CG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals))) +# define ZEND_SCNG_OFFSET (ZEND_EG_OFFSET - (ptrdiff_t) TSRM_ALIGNED_SIZE(sizeof(zend_php_scanner_globals))) +#endif + #define EG_FLAGS_INITIAL (0) #define EG_FLAGS_IN_SHUTDOWN (1<<0) #define EG_FLAGS_OBJECT_STORE_NO_REUSE (1<<1) diff --git a/Zend/zend_globals_macros.h b/Zend/zend_globals_macros.h index bde10a0989d1..2d2948e50a86 100644 --- a/Zend/zend_globals_macros.h +++ b/Zend/zend_globals_macros.h @@ -30,7 +30,7 @@ BEGIN_EXTERN_C() /* Compiler */ #ifdef ZTS -# define CG(v) ZEND_TSRMG_FAST(compiler_globals_offset, zend_compiler_globals *, v) +# define CG(v) ZEND_TSRMG_FAST(ZEND_CG_OFFSET, zend_compiler_globals *, v) #else # define CG(v) (compiler_globals.v) extern ZEND_API struct _zend_compiler_globals compiler_globals; @@ -40,7 +40,7 @@ ZEND_API int zendparse(void); /* Executor */ #ifdef ZTS -# define EG(v) ZEND_TSRMG_FAST(executor_globals_offset, zend_executor_globals *, v) +# define EG(v) ZEND_TSRMG_FAST(ZEND_EG_OFFSET, zend_executor_globals *, v) #else # define EG(v) (executor_globals.v) extern ZEND_API zend_executor_globals executor_globals; @@ -48,7 +48,7 @@ extern ZEND_API zend_executor_globals executor_globals; /* Language Scanner */ #ifdef ZTS -# define LANG_SCNG(v) ZEND_TSRMG_FAST(language_scanner_globals_offset, zend_php_scanner_globals *, v) +# define LANG_SCNG(v) ZEND_TSRMG_FAST(ZEND_SCNG_OFFSET, zend_php_scanner_globals *, v) extern ZEND_API ts_rsrc_id language_scanner_globals_id; extern ZEND_API size_t language_scanner_globals_offset; #else diff --git a/main/main.c b/main/main.c index afb9fd410e82..48e4a757513b 100644 --- a/main/main.c +++ b/main/main.c @@ -2842,6 +2842,12 @@ PHPAPI bool php_tsrm_startup_ex(int expected_threads) { bool ret = tsrm_startup(expected_threads, 1, 0, NULL); php_reserve_tsrm_memory(); + /* Must cover the total size of every ZEND_*_OFFSET global, or the furthest underflows the block. */ + tsrm_reserve_fast_front( + TSRM_ALIGNED_SIZE(sizeof(zend_compiler_globals)) + + TSRM_ALIGNED_SIZE(sizeof(zend_executor_globals)) + + TSRM_ALIGNED_SIZE(sizeof(zend_php_scanner_globals)) + + TSRM_ALIGNED_SIZE(zend_mm_globals_size())); // AG size, exposed through function call (void)ts_resource(0); return ret; } From 2e564a747ccd1836bb77e91a35a807e87765259c Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 28 Jun 2026 18:41:05 -0400 Subject: [PATCH 2/2] Fix GH-17387: crash on unterminated quote in phpdbg lexer In the NORMAL condition a lone unterminated quote matched no rule, so re2c backtracked to a zero-length GENERIC_ID accept. The rule body then computed yyleng - unescape_string(yytext) as 0 - N, underflowing size_t into a multi-gigabyte estrndup. Give a bare quote an explicit one-character accept so the cursor advances and unescape_string is never reached with a malformed token. Fixes GH-17387 Closes GH-22506 --- NEWS | 3 +++ sapi/phpdbg/phpdbg_lexer.l | 7 +++++++ sapi/phpdbg/tests/gh17387.phpt | 8 ++++++++ 3 files changed, 18 insertions(+) create mode 100644 sapi/phpdbg/tests/gh17387.phpt diff --git a/NEWS b/NEWS index 0f39334377e0..531d6a334c05 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,9 @@ PHP NEWS copying, ArrayAccess, stream lookup, directory iteration and extraction. (Weilin Du) +- PHPDBG: + . Fixed bug GH-17387 (Trivial crash in phpdbg lexer). (iliaal) + - Reflection: . Fixed bug GH-22324 (Ignore leading namespace separator in ReflectionParameter::__construct()). (jorgsowa) diff --git a/sapi/phpdbg/phpdbg_lexer.l b/sapi/phpdbg/phpdbg_lexer.l index ba1423c5a4ec..7c6315fc2a05 100644 --- a/sapi/phpdbg/phpdbg_lexer.l +++ b/sapi/phpdbg/phpdbg_lexer.l @@ -166,6 +166,13 @@ INPUT ("\\"[#"']|["]("\\\\"|"\\"["]|[^\n\000"])*["]|[']("\\"[']|"\\\\"|[^\ return T_ID; } +["'] { + phpdbg_init_param(yylval, STR_PARAM); + yylval->str = estrndup(yytext, yyleng); + yylval->len = yyleng; + return T_ID; +} + {INPUT} { phpdbg_init_param(yylval, STR_PARAM); yylval->str = estrdup(yytext); diff --git a/sapi/phpdbg/tests/gh17387.phpt b/sapi/phpdbg/tests/gh17387.phpt new file mode 100644 index 000000000000..af45fd4ba91f --- /dev/null +++ b/sapi/phpdbg/tests/gh17387.phpt @@ -0,0 +1,8 @@ +--TEST-- +GH-17387 (Trivial crash in phpdbg lexer) +--PHPDBG-- +a'; +q +--EXPECT-- +prompt> [The command "a" could not be found] +prompt>