Skip to content

Commit a39beaa

Browse files
committed
Fixed bug #69864 (Segfault in preg_replace_callback)
When preg_replace_callback() is used, cache entries which are in use must not be removed. We ensure that by deploying a simple refcounting mechanism.
1 parent b0954eb commit a39beaa

File tree

2 files changed

+54
-6
lines changed

2 files changed

+54
-6
lines changed

ext/pcre/php_pcre.c

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,14 @@ static PHP_MSHUTDOWN_FUNCTION(pcre)
169169
/* {{{ static pcre_clean_cache */
170170
static int pcre_clean_cache(void *data, void *arg TSRMLS_DC)
171171
{
172+
pcre_cache_entry *pce = (pcre_cache_entry *) data;
172173
int *num_clean = (int *)arg;
173174

174-
if (*num_clean > 0) {
175+
if (*num_clean > 0 && !pce->refcount) {
175176
(*num_clean)--;
176-
return 1;
177+
return ZEND_HASH_APPLY_REMOVE;
177178
} else {
178-
return 0;
179+
return ZEND_HASH_APPLY_KEEP;
179180
}
180181
}
181182
/* }}} */
@@ -446,6 +447,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(char *regex, int regex_le
446447
new_entry.locale = pestrdup(locale, 1);
447448
new_entry.tables = tables;
448449
#endif
450+
new_entry.refcount = 0;
449451

450452
/*
451453
* Interned strings are not duplicated when stored in HashTable,
@@ -550,8 +552,10 @@ static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, int global) /* {{{ *
550552
RETURN_FALSE;
551553
}
552554

555+
pce->refcount++;
553556
php_pcre_match_impl(pce, subject, subject_len, return_value, subpats,
554557
global, ZEND_NUM_ARGS() >= 4, flags, start_offset TSRMLS_CC);
558+
pce->refcount--;
555559
}
556560
/* }}} */
557561

@@ -988,14 +992,18 @@ PHPAPI char *php_pcre_replace(char *regex, int regex_len,
988992
int *result_len, int limit, int *replace_count TSRMLS_DC)
989993
{
990994
pcre_cache_entry *pce; /* Compiled regular expression */
995+
char *result; /* Function result */
991996

992997
/* Compile regex or get it from cache. */
993998
if ((pce = pcre_get_compiled_regex_cache(regex, regex_len TSRMLS_CC)) == NULL) {
994999
return NULL;
9951000
}
996-
997-
return php_pcre_replace_impl(pce, subject, subject_len, replace_val,
1001+
pce->refcount++;
1002+
result = php_pcre_replace_impl(pce, subject, subject_len, replace_val,
9981003
is_callable_replace, result_len, limit, replace_count TSRMLS_CC);
1004+
pce->refcount--;
1005+
1006+
return result;
9991007
}
10001008
/* }}} */
10011009

@@ -1476,7 +1484,9 @@ static PHP_FUNCTION(preg_split)
14761484
RETURN_FALSE;
14771485
}
14781486

1487+
pce->refcount++;
14791488
php_pcre_split_impl(pce, subject, subject_len, return_value, limit_val, flags TSRMLS_CC);
1489+
pce->refcount--;
14801490
}
14811491
/* }}} */
14821492

@@ -1756,8 +1766,10 @@ static PHP_FUNCTION(preg_grep)
17561766
if ((pce = pcre_get_compiled_regex_cache(regex, regex_len TSRMLS_CC)) == NULL) {
17571767
RETURN_FALSE;
17581768
}
1759-
1769+
1770+
pce->refcount++;
17601771
php_pcre_grep_impl(pce, input, return_value, flags TSRMLS_CC);
1772+
pce->refcount--;
17611773
}
17621774
/* }}} */
17631775

ext/pcre/tests/bug69864.phpt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
Bug #69864 (Segfault in preg_replace_callback)
3+
--FILE--
4+
<?php
5+
const PREG_CACHE_SIZE = 4096; // this has to be >= the resp. constant in php_pcre.c
6+
7+
var_dump(preg_replace_callback('/a/', function($m) {
8+
for ($i = 0; $i < PREG_CACHE_SIZE; $i++) {
9+
preg_match('/foo' . $i . 'bar/', '???foo' . $i . 'bar???');
10+
}
11+
return 'b';
12+
}, 'aa'));
13+
var_dump(preg_replace_callback('/a/', function($m) {
14+
for ($i = 0; $i < PREG_CACHE_SIZE; $i++) {
15+
preg_replace('/foo' . $i . 'bar/', 'baz', '???foo' . $i . 'bar???');
16+
}
17+
return 'b';
18+
}, 'aa'));
19+
var_dump(preg_replace_callback('/a/', function($m) {
20+
for ($i = 0; $i < PREG_CACHE_SIZE; $i++) {
21+
preg_split('/foo' . $i . 'bar/', '???foo' . $i . 'bar???');
22+
}
23+
return 'b';
24+
}, 'aa'));
25+
var_dump(preg_replace_callback('/a/', function($m) {
26+
for ($i = 0; $i < PREG_CACHE_SIZE; $i++) {
27+
preg_grep('/foo' . $i . 'bar/', ['???foo' . $i . 'bar???']);
28+
}
29+
return 'b';
30+
}, 'aa'));
31+
?>
32+
--EXPECT--
33+
string(2) "bb"
34+
string(2) "bb"
35+
string(2) "bb"
36+
string(2) "bb"

0 commit comments

Comments
 (0)