Skip to content

Commit f4b2497

Browse files
committed
Allocate temporary PCRE match data using ZMM
Create a separate general context that uses ZMM as allocator and use it to allocate temporary PCRE match data (there is still one global match data). There is no requirement that the match data and the compiled regex / match context use the same general context. This makes sure that we do not leak persistent memory on bailout and fixes oss-fuzz #25296, on which half the libfuzzer runs currently get stuck.
1 parent 9475bcb commit f4b2497

File tree

3 files changed

+49
-11
lines changed

3 files changed

+49
-11
lines changed

ext/pcre/php_pcre.c

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ PHPAPI ZEND_DECLARE_MODULE_GLOBALS(pcre)
5959
#define PCRE_JIT_STACK_MAX_SIZE (192 * 1024)
6060
ZEND_TLS pcre2_jit_stack *jit_stack = NULL;
6161
#endif
62+
/* General context using (infallible) system allocator. */
6263
ZEND_TLS pcre2_general_context *gctx = NULL;
6364
/* These two are global per thread for now. Though it is possible to use these
6465
per pattern. Either one can copy it and use in pce, or one does no global
@@ -173,15 +174,24 @@ static void php_efree_pcre_cache(zval *data) /* {{{ */
173174
/* }}} */
174175

175176
static void *php_pcre_malloc(PCRE2_SIZE size, void *data)
176-
{/*{{{*/
177-
void *p = pemalloc(size, 1);
178-
return p;
179-
}/*}}}*/
177+
{
178+
return pemalloc(size, 1);
179+
}
180180

181181
static void php_pcre_free(void *block, void *data)
182-
{/*{{{*/
182+
{
183183
pefree(block, 1);
184-
}/*}}}*/
184+
}
185+
186+
static void *php_pcre_emalloc(PCRE2_SIZE size, void *data)
187+
{
188+
return emalloc(size);
189+
}
190+
191+
static void php_pcre_efree(void *block, void *data)
192+
{
193+
efree(block);
194+
}
185195

186196
#define PHP_PCRE_PREALLOC_MDATA_SIZE 32
187197

@@ -476,6 +486,11 @@ static PHP_RINIT_FUNCTION(pcre)
476486
mdata_used = 0;
477487
#endif
478488

489+
PCRE_G(gctx_zmm) = pcre2_general_context_create(php_pcre_emalloc, php_pcre_efree, NULL);
490+
if (!PCRE_G(gctx_zmm)) {
491+
return FAILURE;
492+
}
493+
479494
if (PCRE_G(per_request_cache)) {
480495
zend_hash_init(&PCRE_G(pcre_cache), 0, NULL, php_efree_pcre_cache, 0);
481496
}
@@ -486,6 +501,9 @@ static PHP_RINIT_FUNCTION(pcre)
486501

487502
static PHP_RSHUTDOWN_FUNCTION(pcre)
488503
{
504+
pcre2_general_context_free(PCRE_G(gctx_zmm));
505+
PCRE_G(gctx_zmm) = NULL;
506+
489507
if (PCRE_G(per_request_cache)) {
490508
zend_hash_destroy(&PCRE_G(pcre_cache));
491509
}
@@ -1246,7 +1264,7 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str,
12461264
if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
12471265
match_data = mdata;
12481266
} else {
1249-
match_data = pcre2_match_data_create_from_pattern(pce->re, gctx);
1267+
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
12501268
if (!match_data) {
12511269
PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
12521270
if (subpat_names) {
@@ -1617,7 +1635,7 @@ PHPAPI zend_string *php_pcre_replace_impl(pcre_cache_entry *pce, zend_string *su
16171635
if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
16181636
match_data = mdata;
16191637
} else {
1620-
match_data = pcre2_match_data_create_from_pattern(pce->re, gctx);
1638+
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
16211639
if (!match_data) {
16221640
PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
16231641
return NULL;
@@ -1871,7 +1889,7 @@ static zend_string *php_pcre_replace_func_impl(pcre_cache_entry *pce, zend_strin
18711889
mdata_used = 1;
18721890
match_data = mdata;
18731891
} else {
1874-
match_data = pcre2_match_data_create_from_pattern(pce->re, gctx);
1892+
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
18751893
if (!match_data) {
18761894
PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
18771895
if (subpat_names) {
@@ -2519,7 +2537,7 @@ PHPAPI void php_pcre_split_impl(pcre_cache_entry *pce, zend_string *subject_str,
25192537
if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
25202538
match_data = mdata;
25212539
} else {
2522-
match_data = pcre2_match_data_create_from_pattern(pce->re, gctx);
2540+
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
25232541
if (!match_data) {
25242542
PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
25252543
zval_ptr_dtor(return_value);
@@ -2853,7 +2871,7 @@ PHPAPI void php_pcre_grep_impl(pcre_cache_entry *pce, zval *input, zval *return
28532871
if (!mdata_used && num_subpats <= PHP_PCRE_PREALLOC_MDATA_SIZE) {
28542872
match_data = mdata;
28552873
} else {
2856-
match_data = pcre2_match_data_create_from_pattern(pce->re, gctx);
2874+
match_data = pcre2_match_data_create_from_pattern(pce->re, PCRE_G(gctx_zmm));
28572875
if (!match_data) {
28582876
PCRE_G(error_code) = PHP_PCRE_INTERNAL_ERROR;
28592877
return;

ext/pcre/php_pcre.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ ZEND_BEGIN_MODULE_GLOBALS(pcre)
8484
/* Used for unmatched subpatterns in OFFSET_CAPTURE mode */
8585
zval unmatched_null_pair;
8686
zval unmatched_empty_pair;
87+
/* General context using per-request allocator (ZMM). */
88+
pcre2_general_context *gctx_zmm;
8789
ZEND_END_MODULE_GLOBALS(pcre)
8890

8991
PHPAPI ZEND_EXTERN_MODULE_GLOBALS(pcre)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
preg_replace_callback() should not leak persistent memory on fatal error
3+
--FILE--
4+
<?php
5+
6+
function test() {}
7+
8+
preg_replace_callback('/a/', function($matches) {
9+
preg_replace_callback('/x/', function($matches) {
10+
function test() {} // Trigger a fatal error.
11+
return 'y';
12+
}, 'x');
13+
return 'b';
14+
}, 'a');
15+
16+
?>
17+
--EXPECTF--
18+
Fatal error: Cannot redeclare test() (previously declared in %s on line %d

0 commit comments

Comments
 (0)