@@ -49,10 +49,14 @@ char *php_pcre_version;
49
49
50
50
struct _pcre_cache_entry {
51
51
pcre2_code * re ;
52
- /* Pointer is not NULL when there are named captures.
53
- * Length is equal to capture_count + 1 to account for capture group 0. */
52
+ /* Pointer is not NULL (during request) when there are named captures.
53
+ * Length is equal to capture_count + 1 to account for capture group 0.
54
+ * This table cache is only valid during request.
55
+ * Trying to store this over multiple requests causes issues when the keys are exposed in user arrays
56
+ * (see GH-17122 and GH-17132). */
54
57
zend_string * * subpats_table ;
55
58
uint32_t preg_options ;
59
+ uint32_t name_count ;
56
60
uint32_t capture_count ;
57
61
uint32_t compile_options ;
58
62
uint32_t refcount ;
@@ -478,6 +482,14 @@ static PHP_RINIT_FUNCTION(pcre)
478
482
479
483
static PHP_RSHUTDOWN_FUNCTION (pcre )
480
484
{
485
+ pcre_cache_entry * pce ;
486
+ ZEND_HASH_MAP_FOREACH_PTR (& PCRE_G (pcre_cache ), pce ) {
487
+ if (pce -> subpats_table ) {
488
+ free_subpats_table (pce -> subpats_table , pce -> capture_count + 1 );
489
+ pce -> subpats_table = NULL ;
490
+ }
491
+ } ZEND_HASH_FOREACH_END ();
492
+
481
493
pcre2_general_context_free (PCRE_G (gctx_zmm ));
482
494
PCRE_G (gctx_zmm ) = NULL ;
483
495
@@ -509,10 +521,10 @@ static void free_subpats_table(zend_string **subpat_names, uint32_t num_subpats)
509
521
uint32_t i ;
510
522
for (i = 0 ; i < num_subpats ; i ++ ) {
511
523
if (subpat_names [i ]) {
512
- zend_string_release_ex (subpat_names [i ], true );
524
+ zend_string_release_ex (subpat_names [i ], false );
513
525
}
514
526
}
515
- pefree (subpat_names , true );
527
+ efree (subpat_names );
516
528
}
517
529
518
530
/* {{{ static make_subpats_table */
@@ -531,24 +543,25 @@ static zend_string **make_subpats_table(uint32_t name_cnt, pcre_cache_entry *pce
531
543
return NULL ;
532
544
}
533
545
534
- subpat_names = pecalloc (num_subpats , sizeof (zend_string * ), true );
546
+ subpat_names = ecalloc (num_subpats , sizeof (zend_string * ));
535
547
while (ni ++ < name_cnt ) {
536
548
unsigned short name_idx = 0x100 * (unsigned char )name_table [0 ] + (unsigned char )name_table [1 ];
537
549
const char * name = name_table + 2 ;
538
- /* Note: this makes a persistent string when the cache is not request-based because the string
539
- * has to outlive the request. In that case, they will only be used within this thread
540
- * and never be shared.
541
- * Although we will be storing them in user-exposed arrays, they cannot cause problems
542
- * because they only live in this thread and the last reference is deleted on shutdown
543
- * instead of by user code. */
544
- subpat_names [name_idx ] = zend_string_init (name , strlen (name ), true);
545
- GC_MAKE_PERSISTENT_LOCAL (subpat_names [name_idx ]);
550
+ subpat_names [name_idx ] = zend_string_init (name , strlen (name ), false);
546
551
name_table += name_size ;
547
552
}
548
553
return subpat_names ;
549
554
}
550
555
/* }}} */
551
556
557
+ static zend_string * * ensure_subpats_table (uint32_t name_cnt , pcre_cache_entry * pce )
558
+ {
559
+ if (!pce -> subpats_table ) {
560
+ pce -> subpats_table = make_subpats_table (name_cnt , pce );
561
+ }
562
+ return pce -> subpats_table ;
563
+ }
564
+
552
565
/* {{{ static calculate_unit_length */
553
566
/* Calculates the byte length of the next character. Assumes valid UTF-8 for PCRE2_UTF. */
554
567
static zend_always_inline size_t calculate_unit_length (pcre_cache_entry * pce , const char * start )
@@ -820,6 +833,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
820
833
new_entry .preg_options = poptions ;
821
834
new_entry .compile_options = coptions ;
822
835
new_entry .refcount = 0 ;
836
+ new_entry .subpats_table = NULL ;
823
837
824
838
rc = pcre2_pattern_info (re , PCRE2_INFO_CAPTURECOUNT , & new_entry .capture_count );
825
839
if (rc < 0 ) {
@@ -831,8 +845,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
831
845
return NULL ;
832
846
}
833
847
834
- uint32_t name_count ;
835
- rc = pcre2_pattern_info (re , PCRE2_INFO_NAMECOUNT , & name_count );
848
+ rc = pcre2_pattern_info (re , PCRE2_INFO_NAMECOUNT , & new_entry .name_count );
836
849
if (rc < 0 ) {
837
850
if (key != regex ) {
838
851
zend_string_release_ex (key , 0 );
@@ -842,21 +855,6 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo
842
855
return NULL ;
843
856
}
844
857
845
- /* Compute and cache the subpattern table to avoid computing it again over and over. */
846
- if (name_count > 0 ) {
847
- new_entry .subpats_table = make_subpats_table (name_count , & new_entry );
848
- if (!new_entry .subpats_table ) {
849
- if (key != regex ) {
850
- zend_string_release_ex (key , false);
851
- }
852
- /* Warning already emitted by make_subpats_table() */
853
- pcre_handle_exec_error (PCRE2_ERROR_INTERNAL );
854
- return NULL ;
855
- }
856
- } else {
857
- new_entry .subpats_table = NULL ;
858
- }
859
-
860
858
/*
861
859
* Interned strings are not duplicated when stored in HashTable,
862
860
* but all the interned strings created during HTTP request are removed
@@ -971,6 +969,8 @@ static zend_always_inline void populate_match_value(
971
969
972
970
static inline void add_named (
973
971
HashTable * const subpats , zend_string * name , zval * val , bool unmatched ) {
972
+ ZEND_ASSERT (!(GC_FLAGS (name ) & IS_STR_PERSISTENT ));
973
+
974
974
/* If the DUPNAMES option is used, multiple subpatterns might have the same name.
975
975
* In this case we want to preserve the one that actually has a value. */
976
976
if (!unmatched ) {
@@ -1232,8 +1232,11 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str,
1232
1232
* allocate the table only if there are any named subpatterns.
1233
1233
*/
1234
1234
subpat_names = NULL ;
1235
- if (subpats ) {
1236
- subpat_names = pce -> subpats_table ;
1235
+ if (subpats && pce -> name_count > 0 ) {
1236
+ subpat_names = ensure_subpats_table (pce -> name_count , pce );
1237
+ if (UNEXPECTED (!subpat_names )) {
1238
+ RETURN_FALSE ;
1239
+ }
1237
1240
}
1238
1241
1239
1242
matched = 0 ;
@@ -1869,7 +1872,14 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
1869
1872
1870
1873
/* Calculate the size of the offsets array, and allocate memory for it. */
1871
1874
num_subpats = pce -> capture_count + 1 ;
1872
- subpat_names = pce -> subpats_table ;
1875
+ if (pce -> name_count > 0 ) {
1876
+ subpat_names = ensure_subpats_table (pce -> name_count , pce );
1877
+ if (UNEXPECTED (!subpat_names )) {
1878
+ return NULL ;
1879
+ }
1880
+ } else {
1881
+ subpat_names = NULL ;
1882
+ }
1873
1883
1874
1884
alloc_len = 0 ;
1875
1885
result = NULL ;
0 commit comments