diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 9b4388e80871..524a7dd277d2 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -308,6 +308,9 @@ PHP 8.4 INTERNALS UPGRADE NOTES - pcre_get_compiled_regex_cache_ex() now provides an option to collect extra options (from modifiers used in the expression, for example), and calls pcre2_set_compile_extra_options() with those options. + - Removed per-request cache, the cache is now always per process or + per thread depending on whether you use NTS or ZTS. + This was removed due to fundamental ordering issues between destructors. g. ext/standard - Added the php_base64_encode_ex() API with flag parameters, value can be diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 0bd55d319e31..606f64bfc1a3 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2597,10 +2597,6 @@ static void accel_reset_pcre_cache(void) { Bucket *p; - if (PCRE_G(per_request_cache)) { - return; - } - ZEND_HASH_MAP_FOREACH_BUCKET(&PCRE_G(pcre_cache), p) { /* Remove PCRE cache entries with inconsistent keys */ if (zend_accel_in_shm(p->key)) { diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c index ac0ad2c378be..6ad2c964cd4a 100644 --- a/ext/pcre/php_pcre.c +++ b/ext/pcre/php_pcre.c @@ -92,7 +92,7 @@ static MUTEX_T pcre_mt = NULL; ZEND_TLS HashTable char_tables; -static void free_subpats_table(zend_string **subpat_names, uint32_t num_subpats, bool persistent); +static void free_subpats_table(zend_string **subpat_names, uint32_t num_subpats); static void php_pcre_free_char_table(zval *data) {/*{{{*/ @@ -168,25 +168,13 @@ static void php_free_pcre_cache(zval *data) /* {{{ */ pcre_cache_entry *pce = (pcre_cache_entry *) Z_PTR_P(data); if (!pce) return; if (pce->subpats_table) { - free_subpats_table(pce->subpats_table, pce->capture_count + 1, true); + free_subpats_table(pce->subpats_table, pce->capture_count + 1); } pcre2_code_free(pce->re); free(pce); } /* }}} */ -static void php_efree_pcre_cache(zval *data) /* {{{ */ -{ - pcre_cache_entry *pce = (pcre_cache_entry *) Z_PTR_P(data); - if (!pce) return; - if (pce->subpats_table) { - free_subpats_table(pce->subpats_table, pce->capture_count + 1, false); - } - pcre2_code_free(pce->re); - efree(pce); -} -/* }}} */ - static void *php_pcre_malloc(PCRE2_SIZE size, void *data) { return pemalloc(size, 1); @@ -303,12 +291,7 @@ static PHP_GINIT_FUNCTION(pcre) /* {{{ */ { php_pcre_mutex_alloc(); - /* If we're on the CLI SAPI, there will only be one request, so we don't need the - * cache to survive after RSHUTDOWN. */ - pcre_globals->per_request_cache = strcmp(sapi_module.name, "cli") == 0; - if (!pcre_globals->per_request_cache) { - zend_hash_init(&pcre_globals->pcre_cache, 0, NULL, php_free_pcre_cache, 1); - } + zend_hash_init(&pcre_globals->pcre_cache, 0, NULL, php_free_pcre_cache, 1); pcre_globals->backtrack_limit = 0; pcre_globals->recursion_limit = 0; @@ -326,9 +309,7 @@ static PHP_GINIT_FUNCTION(pcre) /* {{{ */ static PHP_GSHUTDOWN_FUNCTION(pcre) /* {{{ */ { - if (!pcre_globals->per_request_cache) { - zend_hash_destroy(&pcre_globals->pcre_cache); - } + zend_hash_destroy(&pcre_globals->pcre_cache); php_pcre_shutdown_pcre2(); zend_hash_destroy(&char_tables); @@ -491,10 +472,6 @@ static PHP_RINIT_FUNCTION(pcre) return FAILURE; } - if (PCRE_G(per_request_cache)) { - zend_hash_init(&PCRE_G(pcre_cache), 0, NULL, php_efree_pcre_cache, 0); - } - return SUCCESS; } /* }}} */ @@ -504,10 +481,6 @@ static PHP_RSHUTDOWN_FUNCTION(pcre) pcre2_general_context_free(PCRE_G(gctx_zmm)); PCRE_G(gctx_zmm) = NULL; - if (PCRE_G(per_request_cache)) { - zend_hash_destroy(&PCRE_G(pcre_cache)); - } - zval_ptr_dtor(&PCRE_G(unmatched_null_pair)); zval_ptr_dtor(&PCRE_G(unmatched_empty_pair)); ZVAL_UNDEF(&PCRE_G(unmatched_null_pair)); @@ -530,18 +503,18 @@ static int pcre_clean_cache(zval *data, void *arg) } /* }}} */ -static void free_subpats_table(zend_string **subpat_names, uint32_t num_subpats, bool persistent) { +static void free_subpats_table(zend_string **subpat_names, uint32_t num_subpats) { uint32_t i; for (i = 0; i < num_subpats; i++) { if (subpat_names[i]) { - zend_string_release_ex(subpat_names[i], persistent); + zend_string_release_ex(subpat_names[i], true); } } - pefree(subpat_names, persistent); + pefree(subpat_names, true); } /* {{{ static make_subpats_table */ -static zend_string **make_subpats_table(uint32_t name_cnt, pcre_cache_entry *pce, bool persistent) +static zend_string **make_subpats_table(uint32_t name_cnt, pcre_cache_entry *pce) { uint32_t num_subpats = pce->capture_count + 1; uint32_t name_size, ni = 0; @@ -556,7 +529,7 @@ static zend_string **make_subpats_table(uint32_t name_cnt, pcre_cache_entry *pce return NULL; } - subpat_names = pecalloc(num_subpats, sizeof(zend_string *), persistent); + subpat_names = pecalloc(num_subpats, sizeof(zend_string *), true); while (ni++ < name_cnt) { unsigned short name_idx = 0x100 * (unsigned char)name_table[0] + (unsigned char)name_table[1]; const char *name = name_table + 2; @@ -566,10 +539,8 @@ static zend_string **make_subpats_table(uint32_t name_cnt, pcre_cache_entry *pce * Although we will be storing them in user-exposed arrays, they cannot cause problems * because they only live in this thread and the last reference is deleted on shutdown * instead of by user code. */ - subpat_names[name_idx] = zend_string_init(name, strlen(name), persistent); - if (persistent) { - GC_MAKE_PERSISTENT_LOCAL(subpat_names[name_idx]); - } + subpat_names[name_idx] = zend_string_init(name, strlen(name), true); + GC_MAKE_PERSISTENT_LOCAL(subpat_names[name_idx]); name_table += name_size; } return subpat_names; @@ -871,7 +842,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo /* Compute and cache the subpattern table to avoid computing it again over and over. */ if (name_count > 0) { - new_entry.subpats_table = make_subpats_table(name_count, &new_entry, !PCRE_G(per_request_cache)); + new_entry.subpats_table = make_subpats_table(name_count, &new_entry); if (!new_entry.subpats_table) { if (key != regex) { zend_string_release_ex(key, false); @@ -892,7 +863,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo * as hash keys especually for this table. * See bug #63180 */ - if (!(GC_FLAGS(key) & IS_STR_PERMANENT) && !PCRE_G(per_request_cache)) { + if (!(GC_FLAGS(key) & IS_STR_PERMANENT)) { zend_string *str = zend_string_init(ZSTR_VAL(key), ZSTR_LEN(key), 1); GC_MAKE_PERSISTENT_LOCAL(str); @@ -963,18 +934,18 @@ PHPAPI void php_pcre_free_match_data(pcre2_match_data *match_data) } }/*}}}*/ -static void init_unmatched_null_pair(void) { +static void init_unmatched_null_pair(zval *pair) { zval val1, val2; ZVAL_NULL(&val1); ZVAL_LONG(&val2, -1); - ZVAL_ARR(&PCRE_G(unmatched_null_pair), zend_new_pair(&val1, &val2)); + ZVAL_ARR(pair, zend_new_pair(&val1, &val2)); } -static void init_unmatched_empty_pair(void) { +static void init_unmatched_empty_pair(zval *pair) { zval val1, val2; ZVAL_EMPTY_STRING(&val1); ZVAL_LONG(&val2, -1); - ZVAL_ARR(&PCRE_G(unmatched_empty_pair), zend_new_pair(&val1, &val2)); + ZVAL_ARR(pair, zend_new_pair(&val1, &val2)); } static zend_always_inline void populate_match_value_str( @@ -1020,15 +991,29 @@ static inline void add_offset_pair( /* Add (match, offset) to the return value */ if (PCRE2_UNSET == start_offset) { if (unmatched_as_null) { - if (Z_ISUNDEF(PCRE_G(unmatched_null_pair))) { - init_unmatched_null_pair(); - } - ZVAL_COPY(&match_pair, &PCRE_G(unmatched_null_pair)); + do { + if (Z_ISUNDEF(PCRE_G(unmatched_null_pair))) { + if (UNEXPECTED(EG(flags) & EG_FLAGS_IN_SHUTDOWN)) { + init_unmatched_null_pair(&match_pair); + break; + } else { + init_unmatched_null_pair(&PCRE_G(unmatched_null_pair)); + } + } + ZVAL_COPY(&match_pair, &PCRE_G(unmatched_null_pair)); + } while (0); } else { - if (Z_ISUNDEF(PCRE_G(unmatched_empty_pair))) { - init_unmatched_empty_pair(); - } - ZVAL_COPY(&match_pair, &PCRE_G(unmatched_empty_pair)); + do { + if (Z_ISUNDEF(PCRE_G(unmatched_empty_pair))) { + if (UNEXPECTED(EG(flags) & EG_FLAGS_IN_SHUTDOWN)) { + init_unmatched_empty_pair(&match_pair); + break; + } else { + init_unmatched_empty_pair(&PCRE_G(unmatched_empty_pair)); + } + } + ZVAL_COPY(&match_pair, &PCRE_G(unmatched_empty_pair)); + } while (0); } } else { zval val1, val2; diff --git a/ext/pcre/php_pcre.h b/ext/pcre/php_pcre.h index 3e526ade551d..e180d68a3d48 100644 --- a/ext/pcre/php_pcre.h +++ b/ext/pcre/php_pcre.h @@ -78,7 +78,6 @@ ZEND_BEGIN_MODULE_GLOBALS(pcre) #ifdef HAVE_PCRE_JIT_SUPPORT bool jit; #endif - bool per_request_cache; php_pcre_error_code error_code; /* Used for unmatched subpatterns in OFFSET_CAPTURE mode */ zval unmatched_null_pair; diff --git a/ext/pcre/tests/gh15205_1.phpt b/ext/pcre/tests/gh15205_1.phpt new file mode 100644 index 000000000000..c5a9b65d2549 --- /dev/null +++ b/ext/pcre/tests/gh15205_1.phpt @@ -0,0 +1,12 @@ +--TEST-- +GH-15205: UAF when destroying RegexIterator after pcre request shutdown +--CREDITS-- +YuanchengJiang +--FILE-- + +--EXPECT-- +Done diff --git a/ext/pcre/tests/gh15205_2.phpt b/ext/pcre/tests/gh15205_2.phpt new file mode 100644 index 000000000000..6d46d85b1004 --- /dev/null +++ b/ext/pcre/tests/gh15205_2.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-15205: UAF when destroying stream after pcre request shutdown +--CREDITS-- +nicolaslegland +--FILE-- + +--EXPECT-- +Close