php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Return to Bug #69864
Patch pcre-refcount revision 2015-06-18 12:49 UTC by cmb@php.net
Patch quick-hack revision 2015-06-17 22:03 UTC by cmb@php.net

Patch pcre-refcount for PCRE related Bug #69864

Patch version 2015-06-18 12:49 UTC

Return to Bug #69864 | Download this patch
This patch renders other patches obsolete

Obsolete patches:

Patch Revisions:

Developer: cmb@php.net

 ext/pcre/php_pcre.c          | 22 +++++++++++++++++-----
 ext/pcre/tests/bug69864.phpt | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 53 insertions(+), 5 deletions(-)

diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c
index c3ceb44..4ac85cc 100644
--- a/ext/pcre/php_pcre.c
+++ b/ext/pcre/php_pcre.c
@@ -187,13 +187,15 @@ static PHP_MSHUTDOWN_FUNCTION(pcre)
 /* {{{ static pcre_clean_cache */
 static int pcre_clean_cache(zval *data, void *arg)
 {
+	/* data is not a zval, but rather a pcre_cache_entry** in PHP 7 */
+	pcre_cache_entry *pce = *((pcre_cache_entry **) data);
 	int *num_clean = (int *)arg;
 
-	if (*num_clean > 0) {
+	if (*num_clean > 0 && !pce->refcount) {
 		(*num_clean)--;
-		return 1;
+		return ZEND_HASH_APPLY_REMOVE;
 	} else {
-		return 0;
+		return ZEND_HASH_APPLY_KEEP;
 	}
 }
 /* }}} */
@@ -461,6 +463,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache(zend_string *regex)
 		NULL;
 	new_entry.tables = tables;
 #endif
+	new_entry.refcount = 0;
 
 	rc = pcre_fullinfo(re, extra, PCRE_INFO_CAPTURECOUNT, &new_entry.capture_count);
 	if (rc < 0) {
@@ -584,8 +587,10 @@ static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, int global) /* {{{ *
 		RETURN_FALSE;
 	}
 
+	pce->refcount++;
 	php_pcre_match_impl(pce, subject->val, (int)subject->len, return_value, subpats,
 		global, ZEND_NUM_ARGS() >= 4, flags, start_offset);
+	pce->refcount--;
 }
 /* }}} */
 
@@ -1017,14 +1022,17 @@ PHPAPI zend_string *php_pcre_replace(zend_string *regex,
 							  int limit, int *replace_count)
 {
 	pcre_cache_entry	*pce;			    /* Compiled regular expression */
+	zend_string 		*result;			/* Function result */
 
 	/* Compile regex or get it from cache. */
 	if ((pce = pcre_get_compiled_regex_cache(regex)) == NULL) {
 		return NULL;
 	}
-
-	return php_pcre_replace_impl(pce, subject_str, subject, subject_len, replace_val,
+	pce->refcount++;
+	result = php_pcre_replace_impl(pce, subject_str, subject, subject_len, replace_val,
 		is_callable_replace, limit, replace_count);
+	pce->refcount--;
+	return result;
 }
 /* }}} */
 
@@ -1643,7 +1651,9 @@ static PHP_FUNCTION(preg_split)
 		RETURN_FALSE;
 	}
 
+	pce->refcount++;
 	php_pcre_split_impl(pce, subject->val, (int)subject->len, return_value, (int)limit_val, flags);
+	pce->refcount--;
 }
 /* }}} */
 
@@ -1950,7 +1960,9 @@ static PHP_FUNCTION(preg_grep)
 		RETURN_FALSE;
 	}
 
+	pce->refcount++;
 	php_pcre_grep_impl(pce, input, return_value, flags);
+	pce->refcount--;
 }
 /* }}} */
 
diff --git a/ext/pcre/tests/bug69864.phpt b/ext/pcre/tests/bug69864.phpt
new file mode 100644
index 0000000..124ece5
--- /dev/null
+++ b/ext/pcre/tests/bug69864.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Bug #69864 (Segfault in preg_replace_callback)
+--FILE--
+<?php
+const PREG_CACHE_SIZE = 4096; // this has to be >= the resp. constant in php_pcre.c
+
+var_dump(preg_replace_callback('/a/', function($m) {
+    for ($i = 0; $i < PREG_CACHE_SIZE; $i++) {
+        preg_match('/foo' . $i . 'bar/', '???foo' . $i . 'bar???');
+    }
+    return 'b';
+}, 'aa'));
+var_dump(preg_replace_callback('/a/', function($m) {
+    for ($i = 0; $i < PREG_CACHE_SIZE; $i++) {
+        preg_replace('/foo' . $i . 'bar/', 'baz', '???foo' . $i . 'bar???');
+    }
+    return 'b';
+}, 'aa'));
+var_dump(preg_replace_callback('/a/', function($m) {
+    for ($i = 0; $i < PREG_CACHE_SIZE; $i++) {
+        preg_split('/foo' . $i . 'bar/', '???foo' . $i . 'bar???');
+    }
+    return 'b';
+}, 'aa'));
+var_dump(preg_replace_callback('/a/', function($m) {
+    for ($i = 0; $i < PREG_CACHE_SIZE; $i++) {
+        preg_grep('/foo' . $i . 'bar/', ['???foo' . $i . 'bar???']);
+    }
+    return 'b';
+}, 'aa'));
+?>
+--EXPECT--
+string(2) "bb"
+string(2) "bb"
+string(2) "bb"
+string(2) "bb"
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Mar 28 10:01:26 2024 UTC